diff options
216 files changed, 5365 insertions, 1096 deletions
diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index b55837e646..35b9130aa3 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -1017,7 +1017,7 @@ reviewers the changes you've made that may not be as visible. You will also need to go and find the Message-Id of your previous cover letter. You can either note it when you send the first series, from the output of `git send-email`, or you can look it up on the -https://public-inbox.org/git[mailing list]. Find your cover letter in the +https://lore.kernel.org/git[mailing list]. Find your cover letter in the archives, click on it, then click "permalink" or "raw" to reveal the Message-Id header. It should match: diff --git a/Documentation/RelNotes/1.6.2.txt b/Documentation/RelNotes/1.6.2.txt index ad060f4f89..980adfb315 100644 --- a/Documentation/RelNotes/1.6.2.txt +++ b/Documentation/RelNotes/1.6.2.txt @@ -11,7 +11,7 @@ push running this release will issue a big warning when the configuration variable is missing. Please refer to: http://git.or.cz/gitwiki/GitFaq#non-bare - http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + https://lore.kernel.org/git/7vbptlsuyv.fsf@gitster.siamese.dyndns.org/ for more details on the reason why this change is needed and the transition plan. diff --git a/Documentation/RelNotes/1.6.3.txt b/Documentation/RelNotes/1.6.3.txt index 418c685cf8..4bcff945e0 100644 --- a/Documentation/RelNotes/1.6.3.txt +++ b/Documentation/RelNotes/1.6.3.txt @@ -11,7 +11,7 @@ push running this release will issue a big warning when the configuration variable is missing. Please refer to: http://git.or.cz/gitwiki/GitFaq#non-bare - http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + https://lore.kernel.org/git/7vbptlsuyv.fsf@gitster.siamese.dyndns.org/ for more details on the reason why this change is needed and the transition plan. diff --git a/Documentation/RelNotes/1.6.4.txt b/Documentation/RelNotes/1.6.4.txt index 7a904419f7..a2a34b43a7 100644 --- a/Documentation/RelNotes/1.6.4.txt +++ b/Documentation/RelNotes/1.6.4.txt @@ -11,7 +11,7 @@ push running this release will issue a big warning when the configuration variable is missing. Please refer to: http://git.or.cz/gitwiki/GitFaq#non-bare - http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + https://lore.kernel.org/git/7vbptlsuyv.fsf@gitster.siamese.dyndns.org/ for more details on the reason why this change is needed and the transition plan. diff --git a/Documentation/RelNotes/1.6.5.txt b/Documentation/RelNotes/1.6.5.txt index ee141c19ad..6c7f7da7eb 100644 --- a/Documentation/RelNotes/1.6.5.txt +++ b/Documentation/RelNotes/1.6.5.txt @@ -22,7 +22,7 @@ push running this release will issue a big warning when the configuration variable is missing. Please refer to: http://git.or.cz/gitwiki/GitFaq#non-bare - http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + https://lore.kernel.org/git/7vbptlsuyv.fsf@gitster.siamese.dyndns.org/ for more details on the reason why this change is needed and the transition plan. diff --git a/Documentation/RelNotes/1.6.6.txt b/Documentation/RelNotes/1.6.6.txt index c50b59c495..3ed1e01433 100644 --- a/Documentation/RelNotes/1.6.6.txt +++ b/Documentation/RelNotes/1.6.6.txt @@ -64,7 +64,7 @@ users will fare this time. Please refer to: http://git.or.cz/gitwiki/GitFaq#non-bare - http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + https://lore.kernel.org/git/7vbptlsuyv.fsf@gitster.siamese.dyndns.org/ for more details on the reason why this change is needed and the transition process that already took place so far. diff --git a/Documentation/RelNotes/1.8.4.txt b/Documentation/RelNotes/1.8.4.txt index 02f681b710..255e185af6 100644 --- a/Documentation/RelNotes/1.8.4.txt +++ b/Documentation/RelNotes/1.8.4.txt @@ -58,7 +58,7 @@ Foreign interfaces, subsystems and ports. credential helper interface from Git.pm. * Update build for Cygwin 1.[57]. Torsten Bögershausen reports that - this is fine with Cygwin 1.7 ($gmane/225824) so let's try moving it + this is fine with Cygwin 1.7 (cf. <51A606A0.5060101@web.de>) so let's try moving it ahead. * The credential helper to talk to keychain on OS X (in contrib/) has diff --git a/Documentation/RelNotes/2.14.6.txt b/Documentation/RelNotes/2.14.6.txt new file mode 100644 index 0000000000..72b7af6799 --- /dev/null +++ b/Documentation/RelNotes/2.14.6.txt @@ -0,0 +1,54 @@ +Git v2.14.6 Release Notes +========================= + +This release addresses the security issues CVE-2019-1348, +CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, +CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387. + +Fixes since v2.14.5 +------------------- + + * CVE-2019-1348: + The --export-marks option of git fast-import is exposed also via + the in-stream command feature export-marks=... and it allows + overwriting arbitrary paths. + + * CVE-2019-1349: + When submodules are cloned recursively, under certain circumstances + Git could be fooled into using the same Git directory twice. We now + require the directory to be empty. + + * CVE-2019-1350: + Incorrect quoting of command-line arguments allowed remote code + execution during a recursive clone in conjunction with SSH URLs. + + * CVE-2019-1351: + While the only permitted drive letters for physical drives on + Windows are letters of the US-English alphabet, this restriction + does not apply to virtual drives assigned via subst <letter>: + <path>. Git mistook such paths for relative paths, allowing writing + outside of the worktree while cloning. + + * CVE-2019-1352: + Git was unaware of NTFS Alternate Data Streams, allowing files + inside the .git/ directory to be overwritten during a clone. + + * CVE-2019-1353: + When running Git in the Windows Subsystem for Linux (also known as + "WSL") while accessing a working directory on a regular Windows + drive, none of the NTFS protections were active. + + * CVE-2019-1354: + Filenames on Linux/Unix can contain backslashes. On Windows, + backslashes are directory separators. Git did not use to refuse to + write out tracked files with such filenames. + + * CVE-2019-1387: + Recursive clones are currently affected by a vulnerability that is + caused by too-lax validation of submodule names, allowing very + targeted attacks via remote code execution in recursive clones. + +Credit for finding these vulnerabilities goes to Microsoft Security +Response Center, in particular to Nicolas Joly. The `fast-import` +fixes were provided by Jeff King, the other fixes by Johannes +Schindelin with help from Garima Singh. diff --git a/Documentation/RelNotes/2.15.4.txt b/Documentation/RelNotes/2.15.4.txt new file mode 100644 index 0000000000..dc241cba34 --- /dev/null +++ b/Documentation/RelNotes/2.15.4.txt @@ -0,0 +1,11 @@ +Git v2.15.4 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6 to address +the security issues CVE-2019-1348, CVE-2019-1349, CVE-2019-1350, +CVE-2019-1351, CVE-2019-1352, CVE-2019-1353, CVE-2019-1354, and +CVE-2019-1387; see the release notes for that version for details. + +In conjunction with a vulnerability that was fixed in v2.20.2, +`.gitmodules` is no longer allowed to contain entries of the form +`submodule.<name>.update=!command`. diff --git a/Documentation/RelNotes/2.16.6.txt b/Documentation/RelNotes/2.16.6.txt new file mode 100644 index 0000000000..438306e60b --- /dev/null +++ b/Documentation/RelNotes/2.16.6.txt @@ -0,0 +1,8 @@ +Git v2.16.6 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6 and in +v2.15.4 addressing the security issues CVE-2019-1348, CVE-2019-1349, +CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, CVE-2019-1353, +CVE-2019-1354, and CVE-2019-1387; see the release notes for those +versions for details. diff --git a/Documentation/RelNotes/2.17.3.txt b/Documentation/RelNotes/2.17.3.txt new file mode 100644 index 0000000000..5a46c94271 --- /dev/null +++ b/Documentation/RelNotes/2.17.3.txt @@ -0,0 +1,12 @@ +Git v2.17.3 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6 and in +v2.15.4 addressing the security issues CVE-2019-1348, CVE-2019-1349, +CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, CVE-2019-1353, +CVE-2019-1354, and CVE-2019-1387; see the release notes for those +versions for details. + +In addition, `git fsck` was taught to identify `.gitmodules` entries +of the form `submodule.<name>.update=!command`, which have been +disallowed in v2.15.4. diff --git a/Documentation/RelNotes/2.18.2.txt b/Documentation/RelNotes/2.18.2.txt new file mode 100644 index 0000000000..98b168aade --- /dev/null +++ b/Documentation/RelNotes/2.18.2.txt @@ -0,0 +1,8 @@ +Git v2.18.2 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4 +and in v2.17.3, addressing the security issues CVE-2019-1348, +CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, +CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387; see the release notes +for those versions for details. diff --git a/Documentation/RelNotes/2.19.3.txt b/Documentation/RelNotes/2.19.3.txt new file mode 100644 index 0000000000..92d7f89de6 --- /dev/null +++ b/Documentation/RelNotes/2.19.3.txt @@ -0,0 +1,8 @@ +Git v2.19.3 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4 +and in v2.17.3, addressing the security issues CVE-2019-1348, +CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, +CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387; see the release notes +for those versions for details. diff --git a/Documentation/RelNotes/2.20.2.txt b/Documentation/RelNotes/2.20.2.txt new file mode 100644 index 0000000000..8e680cb9fb --- /dev/null +++ b/Documentation/RelNotes/2.20.2.txt @@ -0,0 +1,18 @@ +Git v2.20.2 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4 +and in v2.17.3, addressing the security issues CVE-2019-1348, +CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, +CVE-2019-1353, CVE-2019-1354, and CVE-2019-1387; see the release notes +for those versions for details. + +The change to disallow `submodule.<name>.update=!command` entries in +`.gitmodules` which was introduced v2.15.4 (and for which v2.17.3 +added explicit fsck checks) fixes the vulnerability in v2.20.x where a +recursive clone followed by a submodule update could execute code +contained within the repository without the user explicitly having +asked for that (CVE-2019-19604). + +Credit for finding this vulnerability goes to Joern Schneeweisz, +credit for the fixes goes to Jonathan Nieder. diff --git a/Documentation/RelNotes/2.21.1.txt b/Documentation/RelNotes/2.21.1.txt new file mode 100644 index 0000000000..b7594151e4 --- /dev/null +++ b/Documentation/RelNotes/2.21.1.txt @@ -0,0 +1,12 @@ +Git v2.21.1 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4, +v2.17.3 and in v2.20.2, addressing the security issues CVE-2019-1348, +CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, CVE-2019-1352, +CVE-2019-1353, CVE-2019-1354, CVE-2019-1387, and CVE-2019-19604; +see the release notes for those versions for details. + +Additionally, this version also includes a couple of fixes for the +Windows-specific quoting of command-line arguments when Git executes +a Unix shell on Windows. diff --git a/Documentation/RelNotes/2.22.2.txt b/Documentation/RelNotes/2.22.2.txt new file mode 100644 index 0000000000..940a23f0d9 --- /dev/null +++ b/Documentation/RelNotes/2.22.2.txt @@ -0,0 +1,8 @@ +Git v2.22.2 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4, +v2.17.3, v2.20.2 and in v2.21.1, addressing the security issues +CVE-2019-1348, CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, +CVE-2019-1352, CVE-2019-1353, CVE-2019-1354, CVE-2019-1387, and +CVE-2019-19604; see the release notes for those versions for details. diff --git a/Documentation/RelNotes/2.23.1.txt b/Documentation/RelNotes/2.23.1.txt new file mode 100644 index 0000000000..2083b492ce --- /dev/null +++ b/Documentation/RelNotes/2.23.1.txt @@ -0,0 +1,8 @@ +Git v2.23.1 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4, +v2.17.3, v2.20.2 and in v2.21.1, addressing the security issues +CVE-2019-1348, CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, +CVE-2019-1352, CVE-2019-1353, CVE-2019-1354, CVE-2019-1387, and +CVE-2019-19604; see the release notes for those versions for details. diff --git a/Documentation/RelNotes/2.24.1.txt b/Documentation/RelNotes/2.24.1.txt new file mode 100644 index 0000000000..18104850fe --- /dev/null +++ b/Documentation/RelNotes/2.24.1.txt @@ -0,0 +1,8 @@ +Git v2.24.1 Release Notes +========================= + +This release merges up the fixes that appear in v2.14.6, v2.15.4, +v2.17.3, v2.20.2 and in v2.21.1, addressing the security issues +CVE-2019-1348, CVE-2019-1349, CVE-2019-1350, CVE-2019-1351, +CVE-2019-1352, CVE-2019-1353, CVE-2019-1354, CVE-2019-1387, and +CVE-2019-19604; see the release notes for those versions for details. diff --git a/Documentation/RelNotes/2.25.0.txt b/Documentation/RelNotes/2.25.0.txt index e4bb07ac25..19d1341913 100644 --- a/Documentation/RelNotes/2.25.0.txt +++ b/Documentation/RelNotes/2.25.0.txt @@ -39,6 +39,34 @@ UI, Workflows & Features option of "git rebase" can take its argument as the value of the option. + * The userdiff machinery has been taught that "async def" is another + way to begin a "function" in Python. + + * "git range-diff" learned to take the "--notes=<ref>" and the + "--no-notes" options to control the commit notes included in the + log message that gets compared. + + * "git rev-parse --show-toplevel" run outside of any working tree did + not error out, which has been corrected. + + * A few commands learned to take the pathspec from the + standard input or a named file, instead of taking it as the command + line arguments. + + * "git rebase -i" learned a few options that are known by "git + rebase" proper. + + * "git submodule" learned a subcommand "set-url". + + * "git log" family learned "--pretty=reference" that gives the name + of a commit in the format that is often used to refer to it in log + messages. + + * The interaction between "git clone --recurse-submodules" and + alternate object store was ill-designed. The documentation and + code have been taught to make more clear recommendations when the + users see failures. + Performance, Internal Implementation, Development Support etc. @@ -66,6 +94,44 @@ Performance, Internal Implementation, Development Support etc. * Handling of commit objects that use non UTF-8 encoding during "rebase -i" has been improved. + * The beginning of rewriting "git add -i" in C. + + * A label used in the todo list that are generated by "git rebase + --rebase-merges" is used as a part of a refname; the logic to come + up with the label has been tightened to avoid names that cannot be + used as such. + + * The logic to avoid duplicate label names generated by "git rebase + --rebase-merges" forgot that the machinery itself uses "onto" as a + label name, which must be avoided by auto-generated labels, which + has been corrected. + + * We have had compatibility fallback macro definitions for "PRIuMAX", + "PRIu32", etc. but did not for "PRIdMAX", while the code used the + last one apparently without any hiccup reported recently. The + fallback macro definitions for these <inttypes.h> macros that must + appear in C99 systems have been removed. + + * Recently we have declared that GIT_TEST_* variables take the + usual boolean values (it used to be that some used "non-empty + means true" and taking GIT_TEST_VAR=YesPlease as true); make + sure we notice and fail when non-bool strings are given to + these variables. + + * Users of oneway_merge() (like "reset --hard") learned to take + advantage of fsmonitor to avoid unnecessary lstat(2) calls. + + * Performance tweak on "git push" into a repository with many refs + that point at objects we have never heard of. + + * PerfTest fix to avoid stale result mixed up with the latest round + of test results. + + * Hide lower-level verify_signed-buffer() API as a pure helper to + implement the public check_signature() function, in order to + encourage new callers to use the correct and more strict + validation. + Fixes since v2.24 ----------------- @@ -139,6 +205,62 @@ Fixes since v2.24 based on end-user input have been corrected. (merge a376e37b2c jk/gitweb-anti-xss later to maint). + * CI jobs for macOS has been made less chatty when updating perforce + package used during testing. + (merge 0dbc4a0edf jc/azure-ci-osx-fix-fix later to maint). + + * "git unpack-objects" used to show progress based only on the number + of received and unpacked objects, which stalled when it has to + handle an unusually large object. It now shows the throughput as + well. + (merge bae60ba7e9 sg/unpack-progress-throughput later to maint). + + * The sequencer machinery compared the HEAD and the state it is + attempting to commit to decide if the result would be a no-op + commit, even when amending a commit, which was incorrect, and + has been corrected. + + * The code to parse GPG output used to assume incorrectly that the + finterprint for the primary key would always be present for a valid + signature, which has been corrected. + (merge 67a6ea6300 hi/gpg-optional-pkfp-fix later to maint). + + * "git submodule status" and "git submodule status --cached" show + different things, but the documentation did not cover them + correctly, which has been corrected. + (merge 8d483c8408 mg/doc-submodule-status-cached later to maint). + + * "git reset --patch $object" without any pathspec should allow a + tree object to be given, but incorrectly required a committish, + which has been corrected. + + * "git submodule status" that is run from a subdirectory of the + superproject did not work well, which has been corrected. + (merge 1f3aea22c7 mg/submodule-status-from-a-subdirectory later to maint). + + * The revision walking machinery uses resources like per-object flag + bits that need to be reset before a new iteration of walking + begins, but the resources related to topological walk were not + cleared correctly, which has been corrected. + (merge 0aa0c2b2ec mh/clear-topo-walk-upon-reset later to maint). + + * TravisCI update. + (merge 176441bfb5 sg/osx-force-gcc-9 later to maint). + + * While running "revert" or "cherry-pick --edit" for multiple + commits, a recent regression incorrectly detected "nothing to + commit, working tree clean", instead of replaying the commits, + which has been corrected. + (merge befd4f6a81 sg/assume-no-todo-update-in-cherry-pick later to maint). + + * Work around a issue where a FD that is left open when spawning a + child process and is kept open in the child can interfere with the + operation in the parent process on Windows. + + * One kind of progress messages were always given during commit-graph + generation, instead of following the "if it takes more than two + seconds, show progress" pattern, which has been corrected. + * Other code cleanup, docfix, build fix, etc. (merge 80736d7c5e jc/am-show-current-patch-docfix later to maint). (merge 8b656572ca sg/commit-graph-usage-fix later to maint). @@ -153,3 +275,16 @@ Fixes since v2.24 (merge 51bd6be32d rs/use-copy-array-in-mingw-shell-command-preparation later to maint). (merge b018719927 ma/t7004 later to maint). (merge 932757b0cc ar/install-doc-update-cmds-needing-the-shell later to maint). + (merge 46efd28be1 ep/guard-kset-tar-headers later to maint). + (merge 9e5afdf997 ec/fetch-mark-common-refs-trace2 later to maint). + (merge f0e58b3fe8 pb/submodule-update-fetches later to maint). + (merge 2a02262078 dl/t5520-cleanup later to maint). + (merge a4fb016ba1 js/pkt-line-h-typofix later to maint). + (merge 54a7a64613 rs/simplify-prepare-cmd later to maint). + (merge 3eae30e464 jk/lore-is-the-archive later to maint). + (merge 14b7664df8 dl/lore-is-the-archive later to maint). + (merge 0e40a73a4c po/bundle-doc-clonable later to maint). + (merge e714b898c6 as/t7812-missing-redirects-fix later to maint). + (merge 528d9e6d01 jk/perf-wo-git-dot-pm later to maint). + (merge fc42f20e24 sg/test-squelch-noise-in-commit-bulk later to maint). + (merge c64368e3a2 bc/t9001-zsh-in-posix-emulation-mode later to maint). diff --git a/Documentation/RelNotes/2.5.0.txt b/Documentation/RelNotes/2.5.0.txt index 87044504c5..84723f912a 100644 --- a/Documentation/RelNotes/2.5.0.txt +++ b/Documentation/RelNotes/2.5.0.txt @@ -172,7 +172,8 @@ Performance, Internal Implementation, Development Support etc. incorrect patch text to "git apply". Add tests to demonstrate this. - I have a slight suspicion that this may be $gmane/87202 coming back + I have a slight suspicion that this may be + cf. <7vtzf77wjp.fsf@gitster.siamese.dyndns.org> coming back and biting us (I seem to have said "let's run with this and see what happens" back then). diff --git a/Documentation/RelNotes/2.7.3.txt b/Documentation/RelNotes/2.7.3.txt index 6adf038915..f618d71efd 100644 --- a/Documentation/RelNotes/2.7.3.txt +++ b/Documentation/RelNotes/2.7.3.txt @@ -20,7 +20,7 @@ Fixes since v2.7.2 tests. * "git show 'HEAD:Foo[BAR]Baz'" did not interpret the argument as a - rev, i.e. the object named by the the pathname with wildcard + rev, i.e. the object named by the pathname with wildcard characters in a tree object. * "git rev-parse --git-common-dir" used in the worktree feature diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt index fedd9968e5..a63825ed87 100644 --- a/Documentation/RelNotes/2.8.3.txt +++ b/Documentation/RelNotes/2.8.3.txt @@ -55,8 +55,8 @@ Fixes since v2.8.2 This is necessary to use Git on Windows shared directories, and is already enabled for the MinGW and plain Windows builds. It also has been used in Cygwin packaged versions of Git for quite a while. - See http://thread.gmane.org/gmane.comp.version-control.git/291853 - and http://thread.gmane.org/gmane.comp.version-control.git/275680. + See https://lore.kernel.org/git/20160419091055.GF2345@dinwoodie.org/ + and https://lore.kernel.org/git/20150811100527.GW14466@dinwoodie.org/. * "git replace -e" did not honour "core.editor" configuration. diff --git a/Documentation/RelNotes/2.9.0.txt b/Documentation/RelNotes/2.9.0.txt index b61d36712f..991640119a 100644 --- a/Documentation/RelNotes/2.9.0.txt +++ b/Documentation/RelNotes/2.9.0.txt @@ -368,7 +368,7 @@ notes for details). This is necessary to use Git on Windows shared directories, and is already enabled for the MinGW and plain Windows builds. It also has been used in Cygwin packaged versions of Git for quite a while. - See http://thread.gmane.org/gmane.comp.version-control.git/291853 + See https://lore.kernel.org/git/20160419091055.GF2345@dinwoodie.org/ * "merge-octopus" strategy did not ensure that the index is clean when merge begins. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 1a60cc1329..4515cab519 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -142,19 +142,25 @@ archive, summarize the relevant points of the discussion. [[commit-reference]] If you want to reference a previous commit in the history of a stable -branch, use the format "abbreviated sha1 (subject, date)", -with the subject enclosed in a pair of double-quotes, like this: +branch, use the format "abbreviated hash (subject, date)", like this: .... - Commit f86a374 ("pack-bitmap.c: fix a memleak", 2015-03-30) + Commit f86a374 (pack-bitmap.c: fix a memleak, 2015-03-30) noticed that ... .... The "Copy commit summary" command of gitk can be used to obtain this -format, or this invocation of `git show`: +format (with the subject enclosed in a pair of double-quotes), or this +invocation of `git show`: .... - git show -s --date=short --pretty='format:%h ("%s", %ad)' <commit> + git show -s --pretty=reference <commit> +.... + +or, on an older version of Git without support for --pretty=reference: + +.... + git show -s --date=short --pretty='format:%h (%s, %ad)' <commit> .... [[git-tools]] diff --git a/Documentation/config/add.txt b/Documentation/config/add.txt index 4d753f006e..c9f748f81c 100644 --- a/Documentation/config/add.txt +++ b/Documentation/config/add.txt @@ -5,3 +5,8 @@ add.ignore-errors (deprecated):: option of linkgit:git-add[1]. `add.ignore-errors` is deprecated, as it does not follow the usual naming convention for configuration variables. + +add.interactive.useBuiltin:: + [EXPERIMENTAL] Set to `true` to use the experimental built-in + implementation of the interactive version of linkgit:git-add[1] + instead of the Perl script version. Is `false` by default. diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index 6aaa360202..d4e698cd3f 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -107,4 +107,7 @@ advice.*:: editor input from the user. nestedTag:: Advice shown if a user attempts to recursively tag a tag object. + submoduleAlternateErrorStrategyDie: + Advice shown when a submodule.alternateErrorStrategy option + configured to "die" causes a fatal error. -- diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index 852d2ba37a..ad4fa4dccd 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -559,6 +559,12 @@ core.unsetenvvars:: Defaults to `PERL5LIB` to account for the fact that Git for Windows insists on using its own Perl interpreter. +core.restrictinheritedhandles:: + Windows-only: override whether spawned processes inherit only standard + file handles (`stdin`, `stdout` and `stderr`) or all handles. Can be + `auto`, `true` or `false`. Defaults to `auto`, which means `true` on + Windows 7 and later, and `false` on older Windows versions. + core.createObject:: You can set this to 'link', in which case a hardlink followed by a delete of the source are used to make sure that object creation diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt index 0a1293b051..b33177151c 100644 --- a/Documentation/config/submodule.txt +++ b/Documentation/config/submodule.txt @@ -79,4 +79,6 @@ submodule.alternateLocation:: submodule.alternateErrorStrategy:: Specifies how to treat errors with the alternates for a submodule as computed via `submodule.alternateLocation`. Possible values are - `ignore`, `info`, `die`. Default is `die`. + `ignore`, `info`, `die`. Default is `die`. Note that if set to `ignore` + or `info`, and if there is an error with the computed alternate, the + clone proceeds as if no alternate was specified. diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index 2957bc5e0a..3ba49e85b7 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -1350,9 +1350,9 @@ References - [[[1]]] https://www.nist.gov/sites/default/files/documents/director/planning/report02-3.pdf['The Economic Impacts of Inadequate Infratructure for Software Testing'. Nist Planning Report 02-3], see Executive Summary and Chapter 8. - [[[2]]] http://www.oracle.com/technetwork/java/codeconvtoc-136057.html['Code Conventions for the Java Programming Language'. Sun Microsystems.] - [[[3]]] https://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.] -- [[[4]]] https://public-inbox.org/git/7vps5xsbwp.fsf_-_@assigned-by-dhcp.cox.net/[Junio C Hamano. 'Automated bisect success story'.] +- [[[4]]] https://lore.kernel.org/git/7vps5xsbwp.fsf_-_@assigned-by-dhcp.cox.net/[Junio C Hamano. 'Automated bisect success story'.] - [[[5]]] https://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.] - [[[6]]] https://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.] -- [[[7]]] http://marc.info/?l=linux-kernel&m=119702753411680&w=2[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Linux-kernel mailing list.] +- [[[7]]] https://lore.kernel.org/lkml/20071207113734.GA14598@elte.hu/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Linux-kernel mailing list.] - [[[8]]] https://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.] - [[[9]]] https://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.] diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index ccada80a4a..d34b0964be 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -20,11 +20,14 @@ DESCRIPTION Some workflows require that one or more branches of development on one machine be replicated on another machine, but the two machines cannot be directly connected, and therefore the interactive Git protocols (git, -ssh, http) cannot be used. This command provides support for -'git fetch' and 'git pull' to operate by packaging objects and references -in an archive at the originating machine, then importing those into -another repository using 'git fetch' and 'git pull' -after moving the archive by some means (e.g., by sneakernet). As no +ssh, http) cannot be used. + +The 'git bundle' command packages objects and references in an archive +at the originating machine, which can then be imported into another +repository using 'git fetch', 'git pull', or 'git clone', +after moving the archive by some means (e.g., by sneakernet). + +As no direct connection between the repositories exists, the user must specify a basis for the bundle that is held by the destination repository: the bundle assumes that all objects in the basis are already in the @@ -35,7 +38,7 @@ OPTIONS create [options] <file> <git-rev-list-args>:: Used to create a bundle named 'file'. This requires the - 'git-rev-list-args' arguments to define the bundle contents. + '<git-rev-list-args>' arguments to define the bundle contents. 'options' contains the options specific to the 'git bundle create' subcommand. @@ -121,6 +124,14 @@ It is okay to err on the side of caution, causing the bundle file to contain objects already in the destination, as these are ignored when unpacking at the destination. +`git clone` can use any bundle created without negative refspecs +(e.g., `new`, but not `old..new`). +If you want to match `git clone --mirror`, which would include your +refs such as `refs/remotes/*`, use `--all`. +If you want to provide the same set of refs that a clone directly +from the source repository would get, use `--branches --tags` for +the `<git-rev-list-args>`. + EXAMPLES -------- diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index afa7b75a23..ced5a9beab 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -13,7 +13,8 @@ SYNOPSIS [-F <file> | -m <msg>] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=<author>] [--date=<date>] [--cleanup=<mode>] [--[no-]status] - [-i | -o] [-S[<keyid>]] [--] [<file>...] + [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]] + [-S[<keyid>]] [--] [<pathspec>...] DESCRIPTION ----------- @@ -278,6 +279,19 @@ FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) already been staged. If used together with `--allow-empty` paths are also not required, and an empty commit will be created. +--pathspec-from-file=<file>:: + Pathspec is passed in `<file>` instead of commandline args. If + `<file>` is exactly `-` then standard input is used. Pathspec + elements are separated by LF or CR/LF. Pathspec elements can be + quoted as explained for the configuration variable `core.quotePath` + (see linkgit:git-config[1]). See also `--pathspec-file-nul` and + global `--literal-pathspecs`. + +--pathspec-file-nul:: + Only meaningful with `--pathspec-from-file`. Pathspec elements are + separated with NUL character and all other characters are taken + literally (including newlines and quotes). + -u[<mode>]:: --untracked-files[=<mode>]:: Show untracked files. @@ -345,12 +359,13 @@ changes to tracked files. \--:: Do not interpret any more arguments as options. -<file>...:: - When files are given on the command line, the command - commits the contents of the named files, without - recording the changes already staged. The contents of - these files are also staged for the next commit on top - of what have been staged before. +<pathspec>...:: + When pathspec is given on the command line, commit the contents of + the files that match the pathspec without recording the changes + already added to the index. The contents of these files are also + staged for the next commit on top of what have been staged before. ++ +For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. :git-commit: 1 include::date-formats.txt[] diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index a3f1e0c5e4..7889f95940 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -51,6 +51,21 @@ OPTIONS memory used by fast-import during this run. Showing this output is currently the default, but can be disabled with --quiet. +--allow-unsafe-features:: + Many command-line options can be provided as part of the + fast-import stream itself by using the `feature` or `option` + commands. However, some of these options are unsafe (e.g., + allowing fast-import to access the filesystem outside of the + repository). These options are disabled by default, but can be + allowed by providing this option on the command line. This + currently impacts only the `export-marks`, `import-marks`, and + `import-marks-if-exists` feature commands. ++ + Only enable this option if you trust the program generating the + fast-import stream! This option is enabled automatically for + remote-helpers that use the `import` capability, as they are + already trusted to run their own code. + Options for Frontends ~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt index 006feb7ec1..9701c1e5fd 100644 --- a/Documentation/git-range-diff.txt +++ b/Documentation/git-range-diff.txt @@ -57,6 +57,10 @@ to revert to color all lines according to the outer diff markers See the ``Algorithm`` section below for an explanation why this is needed. +--[no-]notes[=<ref>]:: + This flag is passed to the `git log` program + (see linkgit:git-log[1]) that generates the patches. + <range1> <range2>:: Compare the commits specified by the two ranges, where `<range1>` is considered an older version of `<range2>`. @@ -75,7 +79,7 @@ to revert to color all lines according to the outer diff markers linkgit:git-diff[1]), most notably the `--color=[<when>]` and `--no-color` options. These options are used when generating the "diff between patches", i.e. to compare the author, commit message and diff of -corresponding old/new commits. There is currently no means to tweak the +corresponding old/new commits. There is currently no means to tweak most of the diff options passed to `git log` when generating those patches. OUTPUT STABILITY diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 0c4f038dd6..1d0e2d27cc 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -393,16 +393,31 @@ your branch contains commits which were dropped, this option can be used with `--keep-base` in order to drop those commits from your branch. --ignore-whitespace:: + Behaves differently depending on which backend is selected. ++ +'am' backend: When applying a patch, ignore changes in whitespace in +context lines if necessary. ++ +'interactive' backend: Treat lines with only whitespace changes as +unchanged for the sake of a three-way merge. + --whitespace=<option>:: - These flag are passed to the 'git apply' program + This flag is passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + See also INCOMPATIBLE OPTIONS below. --committer-date-is-author-date:: + Instead of recording the time the rebased commits are + created as the committer date, reuse the author date + as the committer date. This implies --force-rebase. + --ignore-date:: - These flags are passed to 'git am' to easily change the dates - of the rebased commits (see linkgit:git-am[1]). +--reset-author-date:: + By default, the author date of the original commit is used + as the author date for the resulting commit. This option + tells Git to use the current timestamp instead and implies + `--force-rebase`. + See also INCOMPATIBLE OPTIONS below. @@ -539,10 +554,7 @@ INCOMPATIBLE OPTIONS The following options: - * --committer-date-is-author-date - * --ignore-date * --whitespace - * --ignore-whitespace * -C are incompatible with the following options: @@ -565,6 +577,9 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --interactive * --preserve-merges and --signoff * --preserve-merges and --rebase-merges + * --preserve-merges and --ignore-whitespace + * --preserve-merges and --committer-date-is-author-date + * --preserve-merges and --ignore-date * --keep-base and --onto * --keep-base and --root diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 97e0544d9e..932080c55d 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,34 +8,36 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git reset' [-q] [<tree-ish>] [--] <paths>... -'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...] +'git reset' [-q] [<tree-ish>] [--] <pathspec>... +'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] +'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...] 'git reset' [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION ----------- -In the first and second form, copy entries from `<tree-ish>` to the index. -In the third form, set the current branch head (`HEAD`) to `<commit>`, +In the first three forms, copy entries from `<tree-ish>` to the index. +In the last form, set the current branch head (`HEAD`) to `<commit>`, optionally modifying index and working tree to match. The `<tree-ish>`/`<commit>` defaults to `HEAD` in all forms. -'git reset' [-q] [<tree-ish>] [--] <paths>...:: - This form resets the index entries for all `<paths>` to their - state at `<tree-ish>`. (It does not affect the working tree or - the current branch.) +'git reset' [-q] [<tree-ish>] [--] <pathspec>...:: +'git reset' [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]:: + These forms reset the index entries for all paths that match the + `<pathspec>` to their state at `<tree-ish>`. (It does not affect + the working tree or the current branch.) + -This means that `git reset <paths>` is the opposite of `git add -<paths>`. This command is equivalent to -`git restore [--source=<tree-ish>] --staged <paths>...`. +This means that `git reset <pathspec>` is the opposite of `git add +<pathspec>`. This command is equivalent to +`git restore [--source=<tree-ish>] --staged <pathspec>...`. + -After running `git reset <paths>` to update the index entry, you can +After running `git reset <pathspec>` to update the index entry, you can use linkgit:git-restore[1] to check the contents out of the index to the working tree. Alternatively, using linkgit:git-restore[1] and specifying a commit with `--source`, you can copy the contents of a path out of a commit to the index and to the working tree in one go. -'git reset' (--patch | -p) [<tree-ish>] [--] [<paths>...]:: +'git reset' (--patch | -p) [<tree-ish>] [--] [<pathspec>...]:: Interactively select hunks in the difference between the index and `<tree-ish>` (defaults to `HEAD`). The chosen hunks are applied in reverse to the index. @@ -101,6 +103,26 @@ OPTIONS `reset.quiet` config option. `--quiet` and `--no-quiet` will override the default behavior. +--pathspec-from-file=<file>:: + Pathspec is passed in `<file>` instead of commandline args. If + `<file>` is exactly `-` then standard input is used. Pathspec + elements are separated by LF or CR/LF. Pathspec elements can be + quoted as explained for the configuration variable `core.quotePath` + (see linkgit:git-config[1]). See also `--pathspec-file-nul` and + global `--literal-pathspecs`. + +--pathspec-file-nul:: + Only meaningful with `--pathspec-from-file`. Pathspec elements are + separated with NUL character and all other characters are taken + literally (including newlines and quotes). + +\--:: + Do not interpret any more arguments as options. + +<pathspec>...:: + Limits the paths affected by the operation. ++ +For more details, see the 'pathspec' entry in linkgit:gitglossary[7]. EXAMPLES -------- diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 9985477efe..19b12b6d43 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -262,7 +262,8 @@ print a message to stderr and exit with nonzero status. directory. --show-toplevel:: - Show the absolute path of the top-level directory. + Show the absolute path of the top-level directory of the working + tree. If there is no working tree, report an error. --show-superproject-working-tree:: Show the absolute path of the root of the superproject's diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 1f46380af2..22425cbc76 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -16,6 +16,7 @@ SYNOPSIS 'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...) 'git submodule' [--quiet] update [<options>] [--] [<path>...] 'git submodule' [--quiet] set-branch [<options>] [--] <path> +'git submodule' [--quiet] set-url [--] <path> <newurl> 'git submodule' [--quiet] summary [<options>] [--] [<path>...] 'git submodule' [--quiet] foreach [--recursive] <command> 'git submodule' [--quiet] sync [--recursive] [--] [<path>...] @@ -80,6 +81,9 @@ status [--cached] [--recursive] [--] [<path>...]:: does not match the SHA-1 found in the index of the containing repository and `U` if the submodule has merge conflicts. + +If `--cached` is specified, this command will instead print the SHA-1 +recorded in the superproject for each submodule. ++ If `--recursive` is specified, this command will recurse into nested submodules, and show their status as well. + @@ -133,7 +137,8 @@ update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--forc + -- Update the registered submodules to match what the superproject -expects by cloning missing submodules and updating the working tree of +expects by cloning missing submodules, fetching missing commits +in submodules and updating the working tree of the submodules. The "updating" can be done in several ways depending on command line options and the value of `submodule.<name>.update` configuration variable. The command line option takes precedence over @@ -180,6 +185,11 @@ set-branch (-d|--default) [--] <path>:: `--default` option removes the submodule.<name>.branch configuration key, which causes the tracking branch to default to 'master'. +set-url [--] <path> <newurl>:: + Sets the URL of the specified submodule to <newurl>. Then, it will + automatically synchronize the submodule's new remote URL + configuration. + summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]:: Show commit summary between the given commit (defaults to HEAD) and working tree/index. For a submodule in question, a series of commits diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 53774f5b64..6624a14fbd 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -677,7 +677,8 @@ config key: svn.authorsProg -s<strategy>:: --strategy=<strategy>:: -p:: ---preserve-merges:: +--rebase-merges:: +--preserve-merges (DEPRECATED):: These are only used with the 'dcommit' and 'rebase' commands. + Passed directly to 'git rebase' when using 'dcommit' if a diff --git a/Documentation/git.txt b/Documentation/git.txt index 1b678e22db..b1597ac002 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -544,6 +544,10 @@ other a pager. See also the `core.pager` option in linkgit:git-config[1]. +`GIT_PROGRESS_DELAY`:: + A number controlling how many seconds to delay before showing + optional progress indicators. Defaults to 2. + `GIT_EDITOR`:: This environment variable overrides `$EDITOR` and `$VISUAL`. It is used by several Git commands when, on interactive mode, @@ -928,7 +932,7 @@ Reporting Bugs Report bugs to the Git mailing list <git@vger.kernel.org> where the development and maintenance is primarily done. You do not have to be subscribed to the list to send a message there. See the list archive -at https://public-inbox.org/git for previous bug reports and other +at https://lore.kernel.org/git for previous bug reports and other discussions. Issues which are security relevant should be disclosed privately to diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 4b32876b6e..373cfa2264 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -211,8 +211,8 @@ only affects the files in the working tree, but with entries, and with `--cached`, it modifies only the index entries. -See also http://marc.info/?l=git&m=116563135620359 and -http://marc.info/?l=git&m=119150393620273 for further +See also https://lore.kernel.org/git/7v64clg5u9.fsf@assigned-by-dhcp.cox.net/ and +https://lore.kernel.org/git/7vy7ej9g38.fsf@gitster.siamese.dyndns.org/ for further information. Some other commands that also work on files in the working tree and/or diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 953c3876f0..b5d1c05756 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -44,9 +44,8 @@ submodule.<name>.update:: submodule init` to initialize the configuration variable of the same name. Allowed values here are 'checkout', 'rebase', 'merge' or 'none'. See description of 'update' command in - linkgit:git-submodule[1] for their meaning. Note that the - '!command' form is intentionally ignored here for security - reasons. + linkgit:git-submodule[1] for their meaning. For security + reasons, the '!command' form is not accepted here. submodule.<name>.branch:: A remote branch name for tracking updates in the upstream submodule. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 31c6e8d2b8..1a7212ce5a 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -4,7 +4,7 @@ PRETTY FORMATS If the commit is a merge, and if the pretty-format is not 'oneline', 'email' or 'raw', an additional line is inserted before the 'Author:' line. This line begins with -"Merge: " and the sha1s of ancestral commits are printed, +"Merge: " and the hashes of ancestral commits are printed, separated by spaces. Note that the listed commits may not necessarily be the list of the *direct* parent commits if you have limited your view of history: for example, if you are @@ -20,20 +20,20 @@ built-in formats: * 'oneline' - <sha1> <title line> + <hash> <title line> + This is designed to be as compact as possible. * 'short' - commit <sha1> + commit <hash> Author: <author> <title line> * 'medium' - commit <sha1> + commit <hash> Author: <author> Date: <author date> @@ -43,7 +43,7 @@ This is designed to be as compact as possible. * 'full' - commit <sha1> + commit <hash> Author: <author> Commit: <committer> @@ -53,7 +53,7 @@ This is designed to be as compact as possible. * 'fuller' - commit <sha1> + commit <hash> Author: <author> AuthorDate: <author date> Commit: <committer> @@ -63,9 +63,20 @@ This is designed to be as compact as possible. <full commit message> +* 'reference' + + <abbrev hash> (<title line>, <short author date>) ++ +This format is used to refer to another commit in a commit message and +is the same as `--pretty='format:%C(auto)%h (%s, %ad)'`. By default, +the date is formatted with `--date=short` unless another `--date` option +is explicitly specified. As with any `format:` with format +placeholders, its output is not affected by other options like +`--decorate` and `--walk-reflogs`. + * 'email' - From <sha1> <date> + From <hash> <date> From: <author> Date: <author date> Subject: [PATCH] <title line> @@ -75,7 +86,7 @@ This is designed to be as compact as possible. * 'raw' + The 'raw' format shows the entire commit exactly as -stored in the commit object. Notably, the SHA-1s are +stored in the commit object. Notably, the hashes are displayed in full, regardless of whether --abbrev or --no-abbrev are used, and 'parents' information show the true parent commits, without taking grafts or history @@ -172,6 +183,7 @@ The placeholders are: '%at':: author date, UNIX timestamp '%ai':: author date, ISO 8601-like format '%aI':: author date, strict ISO 8601 format +'%as':: author date, short format (`YYYY-MM-DD`) '%cn':: committer name '%cN':: committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) @@ -187,6 +199,7 @@ The placeholders are: '%ct':: committer date, UNIX timestamp '%ci':: committer date, ISO 8601-like format '%cI':: committer date, strict ISO 8601 format +'%cs':: committer date, short format (`YYYY-MM-DD`) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping. '%S':: ref name given on the command line by which the commit was reached diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index e44fc8f738..7a6da6db78 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -3,7 +3,7 @@ Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', - 'full', 'fuller', 'email', 'raw', 'format:<string>' + 'full', 'fuller', 'reference', 'email', 'raw', 'format:<string>' and 'tformat:<string>'. When '<format>' is none of the above, and has '%placeholder' in it, it acts as if '--pretty=tformat:<format>' were given. @@ -57,7 +57,7 @@ message by 4 spaces (i.e. 'medium', which is the default, 'full', and 'fuller'). ifndef::git-rev-list[] ---notes[=<treeish>]:: +--notes[=<ref>]:: Show the notes (see linkgit:git-notes[1]) that annotate the commit, when showing the commit log message. This is the default for `git log`, `git show` and `git whatchanged` commands when @@ -68,8 +68,8 @@ By default, the notes shown are from the notes refs listed in the `core.notesRef` and `notes.displayRef` variables (or corresponding environment overrides). See linkgit:git-config[1] for more details. + -With an optional '<treeish>' argument, use the treeish to find the notes -to display. The treeish can specify the full refname when it begins +With an optional '<ref>' argument, use the ref to find the notes +to display. The ref can specify the full refname when it begins with `refs/notes/`; when it begins with `notes/`, `refs/` and otherwise `refs/notes/` is prefixed to form a full name of the ref. + @@ -85,7 +85,7 @@ being displayed. Examples: "--notes=foo" will show only notes from "--notes --notes=foo --no-notes --notes=bar" will only show notes from "refs/notes/bar". ---show-notes[=<treeish>]:: +--show-notes[=<ref>]:: --[no-]standard-notes:: These options are deprecated. Use the above --notes/--no-notes options instead. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index e37df921b5..bfd02ade99 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -58,7 +58,7 @@ endif::git-rev-list[] `--all-match`). ifndef::git-rev-list[] + -When `--show-notes` is in effect, the message from the notes is +When `--notes` is in effect, the message from the notes is matched as if it were part of the log message. endif::git-rev-list[] @@ -269,7 +269,7 @@ list. exclude (that is, '{caret}commit', 'commit1..commit2', and 'commit1\...commit2' notations cannot be used). + -With `--pretty` format other than `oneline` (for obvious reasons), +With `--pretty` format other than `oneline` and `reference` (for obvious reasons), this causes the output to have two extra lines of information taken from the reflog. The reflog designator in the output may be shown as `ref@{Nth}` (where `Nth` is the reverse-chronological index in the @@ -293,6 +293,8 @@ Under `--pretty=oneline`, the commit message is prefixed with this information on the same line. This option cannot be combined with `--reverse`. See also linkgit:git-reflog[1]. ++ +Under `--pretty=reference`, this information will not be shown at all. --merge:: After a failed merge, show refs that touch files having a diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.txt index 1507117dc2..808fa30b99 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.txt @@ -323,14 +323,14 @@ Related Links [0] https://bugs.chromium.org/p/git/issues/detail?id=8 Chromium work item for: Serialized Commit Graph -[1] https://public-inbox.org/git/20110713070517.GC18566@sigill.intra.peff.net/ +[1] https://lore.kernel.org/git/20110713070517.GC18566@sigill.intra.peff.net/ An abandoned patch that introduced generation numbers. -[2] https://public-inbox.org/git/20170908033403.q7e6dj7benasrjes@sigill.intra.peff.net/ +[2] https://lore.kernel.org/git/20170908033403.q7e6dj7benasrjes@sigill.intra.peff.net/ Discussion about generation numbers on commits and how they interact with fsck. -[3] https://public-inbox.org/git/20170908034739.4op3w4f2ma5s65ku@sigill.intra.peff.net/ +[3] https://lore.kernel.org/git/20170908034739.4op3w4f2ma5s65ku@sigill.intra.peff.net/ More discussion about generation numbers and not storing them inside commit objects. A valuable quote: @@ -342,9 +342,9 @@ Related Links commit objects (i.e., packv4 or something like the "metapacks" I proposed a few years ago)." -[4] https://public-inbox.org/git/20180108154822.54829-1-git@jeffhostetler.com/T/#u +[4] https://lore.kernel.org/git/20180108154822.54829-1-git@jeffhostetler.com/T/#u A patch to remove the ahead-behind calculation from 'status'. -[5] https://public-inbox.org/git/f27db281-abad-5043-6d71-cbb083b1c877@gmail.com/ +[5] https://lore.kernel.org/git/f27db281-abad-5043-6d71-cbb083b1c877@gmail.com/ A discussion of a "two-dimensional graph position" that can allow reading multiple commit-graph chains at the same time. diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt index 8a4596bec1..5b2db3be1e 100644 --- a/Documentation/technical/hash-function-transition.txt +++ b/Documentation/technical/hash-function-transition.txt @@ -730,7 +730,7 @@ adoption. Using hash functions in parallel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -(e.g. https://public-inbox.org/git/22708.8913.864049.452252@chiark.greenend.org.uk/ ) +(e.g. https://lore.kernel.org/git/22708.8913.864049.452252@chiark.greenend.org.uk/ ) Objects newly created would be addressed by the new hash, but inside such an object (e.g. commit) it is still possible to address objects using the old hash function. @@ -783,7 +783,7 @@ bmwill@google.com, jonathantanmy@google.com, jrnieder@gmail.com, sbeller@google.com Initial version sent to -http://public-inbox.org/git/20170304011251.GA26789@aiede.mtv.corp.google.com +http://lore.kernel.org/git/20170304011251.GA26789@aiede.mtv.corp.google.com 2017-03-03 jrnieder@gmail.com Incorporated suggestions from jonathantanmy and sbeller: @@ -820,8 +820,8 @@ Later history: edits. This document history is no longer being maintained as it would now be superfluous to the commit log -[1] http://public-inbox.org/git/CA+55aFzJtejiCjV0e43+9oR3QuJK2PiFiLQemytoLpyJWe6P9w@mail.gmail.com/ -[2] http://public-inbox.org/git/CA+55aFz+gkAsDZ24zmePQuEs1XPS9BP_s8O7Q4wQ7LV7X5-oDA@mail.gmail.com/ -[3] http://public-inbox.org/git/20170306084353.nrns455dvkdsfgo5@sigill.intra.peff.net/ -[4] http://public-inbox.org/git/20170304224936.rqqtkdvfjgyezsht@genre.crustytoothpaste.net -[5] https://public-inbox.org/git/CAJo=hJtoX9=AyLHHpUJS7fueV9ciZ_MNpnEPHUz8Whui6g9F0A@mail.gmail.com/ +[1] http://lore.kernel.org/git/CA+55aFzJtejiCjV0e43+9oR3QuJK2PiFiLQemytoLpyJWe6P9w@mail.gmail.com/ +[2] http://lore.kernel.org/git/CA+55aFz+gkAsDZ24zmePQuEs1XPS9BP_s8O7Q4wQ7LV7X5-oDA@mail.gmail.com/ +[3] http://lore.kernel.org/git/20170306084353.nrns455dvkdsfgo5@sigill.intra.peff.net/ +[4] http://lore.kernel.org/git/20170304224936.rqqtkdvfjgyezsht@genre.crustytoothpaste.net +[5] https://lore.kernel.org/git/CAJo=hJtoX9=AyLHHpUJS7fueV9ciZ_MNpnEPHUz8Whui6g9F0A@mail.gmail.com/ diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt index d7e57639f7..1e31239696 100644 --- a/Documentation/technical/multi-pack-index.txt +++ b/Documentation/technical/multi-pack-index.txt @@ -102,8 +102,8 @@ Related Links [0] https://bugs.chromium.org/p/git/issues/detail?id=6 Chromium work item for: Multi-Pack Index (MIDX) -[1] https://public-inbox.org/git/20180107181459.222909-1-dstolee@microsoft.com/ +[1] https://lore.kernel.org/git/20180107181459.222909-1-dstolee@microsoft.com/ An earlier RFC for the multi-pack-index feature -[2] https://public-inbox.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/ +[2] https://lore.kernel.org/git/alpine.DEB.2.20.1803091557510.23109@alexmv-linux/ Git Merge 2018 Contributor's summit notes (includes discussion of MIDX) diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt index a6034d5610..b9e17e7a28 100644 --- a/Documentation/technical/partial-clone.txt +++ b/Documentation/technical/partial-clone.txt @@ -350,26 +350,26 @@ Related Links [0] https://crbug.com/git/2 Bug#2: Partial Clone -[1] https://public-inbox.org/git/20170113155253.1644-1-benpeart@microsoft.com/ + +[1] https://lore.kernel.org/git/20170113155253.1644-1-benpeart@microsoft.com/ + Subject: [RFC] Add support for downloading blobs on demand + Date: Fri, 13 Jan 2017 10:52:53 -0500 -[2] https://public-inbox.org/git/cover.1506714999.git.jonathantanmy@google.com/ + +[2] https://lore.kernel.org/git/cover.1506714999.git.jonathantanmy@google.com/ + Subject: [PATCH 00/18] Partial clone (from clone to lazy fetch in 18 patches) + Date: Fri, 29 Sep 2017 13:11:36 -0700 -[3] https://public-inbox.org/git/20170426221346.25337-1-jonathantanmy@google.com/ + +[3] https://lore.kernel.org/git/20170426221346.25337-1-jonathantanmy@google.com/ + Subject: Proposal for missing blob support in Git repos + Date: Wed, 26 Apr 2017 15:13:46 -0700 -[4] https://public-inbox.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/ + +[4] https://lore.kernel.org/git/1488999039-37631-1-git-send-email-git@jeffhostetler.com/ + Subject: [PATCH 00/10] RFC Partial Clone and Fetch + Date: Wed, 8 Mar 2017 18:50:29 +0000 -[5] https://public-inbox.org/git/20170505152802.6724-1-benpeart@microsoft.com/ + +[5] https://lore.kernel.org/git/20170505152802.6724-1-benpeart@microsoft.com/ + Subject: [PATCH v7 00/10] refactor the filter process code into a reusable module + Date: Fri, 5 May 2017 11:27:52 -0400 -[6] https://public-inbox.org/git/20170714132651.170708-1-benpeart@microsoft.com/ + +[6] https://lore.kernel.org/git/20170714132651.170708-1-benpeart@microsoft.com/ + Subject: [RFC/PATCH v2 0/1] Add support for downloading blobs on demand + Date: Fri, 14 Jul 2017 09:26:50 -0400 diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt index 4a8be4d144..ceda4bbfda 100644 --- a/Documentation/technical/racy-git.txt +++ b/Documentation/technical/racy-git.txt @@ -51,7 +51,7 @@ of git://git.kernel.org/pub/scm/linux/kernel/git/tglx/history.git only fixes the issue for file systems with exactly 1 ns or 1 s resolution. Other file systems are still broken in current Linux kernels (e.g. CEPH, CIFS, NTFS, UDF), see -https://lkml.org/lkml/2015/6/9/714 +https://lore.kernel.org/lkml/5577240D.7020309@gmail.com/ Racy Git -------- diff --git a/Documentation/urls.txt b/Documentation/urls.txt index bc354fe2dc..1c229d7581 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -53,6 +53,9 @@ These two syntaxes are mostly equivalent, except the former implies --local option. endif::git-clone[] +'git clone', 'git fetch' and 'git pull', but not 'git push', will also +accept a suitable bundle file. See linkgit:git-bundle[1]. + When Git doesn't know how to handle a certain transport protocol, it attempts to use the 'remote-<transport>' remote helper, if one exists. To explicitly request a remote helper, the following syntax diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 06bd8994ee..833652983f 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4574,5 +4574,5 @@ Write a chapter on using plumbing and writing scripts. Alternates, clone -reference, etc. More on recovery from repository corruption. See: - http://marc.info/?l=git&m=117263864820799&w=2 - http://marc.info/?l=git&m=117147855503798&w=2 + https://lore.kernel.org/git/Pine.LNX.4.64.0702272039540.12485@woody.linux-foundation.org/ + https://lore.kernel.org/git/Pine.LNX.4.64.0702141033400.3604@woody.linux-foundation.org/ @@ -481,7 +481,7 @@ all:: # # When DEVELOPER is set, DEVOPTS can be used to control compiler # options. This variable contains keywords separated by -# whitespace. The following keywords are are recognized: +# whitespace. The following keywords are recognized: # # no-error: # @@ -824,6 +824,7 @@ LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat -name '*.h' -print))) LIB_OBJS += abspath.o +LIB_OBJS += add-interactive.o LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o @@ -35,7 +35,7 @@ requests, comments and patches to git@vger.kernel.org (read [Documentation/SubmittingPatches][] for instructions on patch submission). To subscribe to the list, send an email with just "subscribe git" in the body to majordomo@vger.kernel.org. The mailing list archives are -available at <https://public-inbox.org/git/>, +available at <https://lore.kernel.org/git/>, <http://marc.info/?l=git> and other archival sites. Issues which are security relevant should be disclosed privately to diff --git a/add-interactive.c b/add-interactive.c new file mode 100644 index 0000000000..f395d54c08 --- /dev/null +++ b/add-interactive.c @@ -0,0 +1,1153 @@ +#include "cache.h" +#include "add-interactive.h" +#include "color.h" +#include "config.h" +#include "diffcore.h" +#include "revision.h" +#include "refs.h" +#include "string-list.h" +#include "lockfile.h" +#include "dir.h" +#include "run-command.h" + +struct add_i_state { + struct repository *r; + int use_color; + char header_color[COLOR_MAXLEN]; + char help_color[COLOR_MAXLEN]; + char prompt_color[COLOR_MAXLEN]; + char error_color[COLOR_MAXLEN]; + char reset_color[COLOR_MAXLEN]; +}; + +static void init_color(struct repository *r, struct add_i_state *s, + const char *slot_name, char *dst, + const char *default_color) +{ + char *key = xstrfmt("color.interactive.%s", slot_name); + const char *value; + + if (!s->use_color) + dst[0] = '\0'; + else if (repo_config_get_value(r, key, &value) || + color_parse(value, dst)) + strlcpy(dst, default_color, COLOR_MAXLEN); + + free(key); +} + +static void init_add_i_state(struct add_i_state *s, struct repository *r) +{ + const char *value; + + s->r = r; + + if (repo_config_get_value(r, "color.interactive", &value)) + s->use_color = -1; + else + s->use_color = + git_config_colorbool("color.interactive", value); + s->use_color = want_color(s->use_color); + + init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); + init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); + init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE); + init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED); + init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET); +} + +/* + * A "prefix item list" is a list of items that are identified by a string, and + * a unique prefix (if any) is determined for each item. + * + * It is implemented in the form of a pair of `string_list`s, the first one + * duplicating the strings, with the `util` field pointing at a structure whose + * first field must be `size_t prefix_length`. + * + * That `prefix_length` field will be computed by `find_unique_prefixes()`; It + * will be set to zero if no valid, unique prefix could be found. + * + * The second `string_list` is called `sorted` and does _not_ duplicate the + * strings but simply reuses the first one's, with the `util` field pointing at + * the `string_item_list` of the first `string_list`. It will be populated and + * sorted by `find_unique_prefixes()`. + */ +struct prefix_item_list { + struct string_list items; + struct string_list sorted; + int *selected; /* for multi-selections */ + size_t min_length, max_length; +}; +#define PREFIX_ITEM_LIST_INIT \ + { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 } + +static void prefix_item_list_clear(struct prefix_item_list *list) +{ + string_list_clear(&list->items, 1); + string_list_clear(&list->sorted, 0); + FREE_AND_NULL(list->selected); +} + +static void extend_prefix_length(struct string_list_item *p, + const char *other_string, size_t max_length) +{ + size_t *len = p->util; + + if (!*len || memcmp(p->string, other_string, *len)) + return; + + for (;;) { + char c = p->string[*len]; + + /* + * Is `p` a strict prefix of `other`? Or have we exhausted the + * maximal length of the prefix? Or is the current character a + * multi-byte UTF-8 one? If so, there is no valid, unique + * prefix. + */ + if (!c || ++*len > max_length || !isascii(c)) { + *len = 0; + break; + } + + if (c != other_string[*len - 1]) + break; + } +} + +static void find_unique_prefixes(struct prefix_item_list *list) +{ + size_t i; + + if (list->sorted.nr == list->items.nr) + return; + + string_list_clear(&list->sorted, 0); + /* Avoid reallocating incrementally */ + list->sorted.items = xmalloc(st_mult(sizeof(*list->sorted.items), + list->items.nr)); + list->sorted.nr = list->sorted.alloc = list->items.nr; + + for (i = 0; i < list->items.nr; i++) { + list->sorted.items[i].string = list->items.items[i].string; + list->sorted.items[i].util = list->items.items + i; + } + + string_list_sort(&list->sorted); + + for (i = 0; i < list->sorted.nr; i++) { + struct string_list_item *sorted_item = list->sorted.items + i; + struct string_list_item *item = sorted_item->util; + size_t *len = item->util; + + *len = 0; + while (*len < list->min_length) { + char c = item->string[(*len)++]; + + if (!c || !isascii(c)) { + *len = 0; + break; + } + } + + if (i > 0) + extend_prefix_length(item, sorted_item[-1].string, + list->max_length); + if (i + 1 < list->sorted.nr) + extend_prefix_length(item, sorted_item[1].string, + list->max_length); + } +} + +static ssize_t find_unique(const char *string, struct prefix_item_list *list) +{ + int index = string_list_find_insert_index(&list->sorted, string, 1); + struct string_list_item *item; + + if (list->items.nr != list->sorted.nr) + BUG("prefix_item_list in inconsistent state (%"PRIuMAX + " vs %"PRIuMAX")", + (uintmax_t)list->items.nr, (uintmax_t)list->sorted.nr); + + if (index < 0) + item = list->sorted.items[-1 - index].util; + else if (index > 0 && + starts_with(list->sorted.items[index - 1].string, string)) + return -1; + else if (index + 1 < list->sorted.nr && + starts_with(list->sorted.items[index + 1].string, string)) + return -1; + else if (index < list->sorted.nr) + item = list->sorted.items[index].util; + else + return -1; + return item - list->items.items; +} + +struct list_options { + int columns; + const char *header; + void (*print_item)(int i, int selected, struct string_list_item *item, + void *print_item_data); + void *print_item_data; +}; + +static void list(struct add_i_state *s, struct string_list *list, int *selected, + struct list_options *opts) +{ + int i, last_lf = 0; + + if (!list->nr) + return; + + if (opts->header) + color_fprintf_ln(stdout, s->header_color, + "%s", opts->header); + + for (i = 0; i < list->nr; i++) { + opts->print_item(i, selected ? selected[i] : 0, list->items + i, + opts->print_item_data); + + if ((opts->columns) && ((i + 1) % (opts->columns))) { + putchar('\t'); + last_lf = 0; + } + else { + putchar('\n'); + last_lf = 1; + } + } + + if (!last_lf) + putchar('\n'); +} +struct list_and_choose_options { + struct list_options list_opts; + + const char *prompt; + enum { + SINGLETON = (1<<0), + IMMEDIATE = (1<<1), + } flags; + void (*print_help)(struct add_i_state *s); +}; + +#define LIST_AND_CHOOSE_ERROR (-1) +#define LIST_AND_CHOOSE_QUIT (-2) + +/* + * Returns the selected index in singleton mode, the number of selected items + * otherwise. + * + * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF, + * `LIST_AND_CHOOSE_QUIT` is returned. + */ +static ssize_t list_and_choose(struct add_i_state *s, + struct prefix_item_list *items, + struct list_and_choose_options *opts) +{ + int singleton = opts->flags & SINGLETON; + int immediate = opts->flags & IMMEDIATE; + + struct strbuf input = STRBUF_INIT; + ssize_t res = singleton ? LIST_AND_CHOOSE_ERROR : 0; + + if (!singleton) { + free(items->selected); + CALLOC_ARRAY(items->selected, items->items.nr); + } + + if (singleton && !immediate) + BUG("singleton requires immediate"); + + find_unique_prefixes(items); + + for (;;) { + char *p; + + strbuf_reset(&input); + + list(s, &items->items, items->selected, &opts->list_opts); + + color_fprintf(stdout, s->prompt_color, "%s", opts->prompt); + fputs(singleton ? "> " : ">> ", stdout); + fflush(stdout); + + if (strbuf_getline(&input, stdin) == EOF) { + putchar('\n'); + if (immediate) + res = LIST_AND_CHOOSE_QUIT; + break; + } + strbuf_trim(&input); + + if (!input.len) + break; + + if (!strcmp(input.buf, "?")) { + opts->print_help(s); + continue; + } + + p = input.buf; + for (;;) { + size_t sep = strcspn(p, " \t\r\n,"); + int choose = 1; + /* `from` is inclusive, `to` is exclusive */ + ssize_t from = -1, to = -1; + + if (!sep) { + if (!*p) + break; + p++; + continue; + } + + /* Input that begins with '-'; de-select */ + if (*p == '-') { + choose = 0; + p++; + sep--; + } + + if (sep == 1 && *p == '*') { + from = 0; + to = items->items.nr; + } else if (isdigit(*p)) { + char *endp; + /* + * A range can be specified like 5-7 or 5-. + * + * Note: `from` is 0-based while the user input + * is 1-based, hence we have to decrement by + * one. We do not have to decrement `to` even + * if it is 0-based because it is an exclusive + * boundary. + */ + from = strtoul(p, &endp, 10) - 1; + if (endp == p + sep) + to = from + 1; + else if (*endp == '-') { + to = strtoul(++endp, &endp, 10); + /* extra characters after the range? */ + if (endp != p + sep) + from = -1; + } + } + + if (p[sep]) + p[sep++] = '\0'; + if (from < 0) { + from = find_unique(p, items); + if (from >= 0) + to = from + 1; + } + + if (from < 0 || from >= items->items.nr || + (singleton && from + 1 != to)) { + color_fprintf_ln(stdout, s->error_color, + _("Huh (%s)?"), p); + break; + } else if (singleton) { + res = from; + break; + } + + if (to > items->items.nr) + to = items->items.nr; + + for (; from < to; from++) + if (items->selected[from] != choose) { + items->selected[from] = choose; + res += choose ? +1 : -1; + } + + p += sep; + } + + if ((immediate && res != LIST_AND_CHOOSE_ERROR) || + !strcmp(input.buf, "*")) + break; + } + + strbuf_release(&input); + return res; +} + +struct adddel { + uintmax_t add, del; + unsigned seen:1, unmerged:1, binary:1; +}; + +struct file_item { + size_t prefix_length; + struct adddel index, worktree; +}; + +static void add_file_item(struct string_list *files, const char *name) +{ + struct file_item *item = xcalloc(sizeof(*item), 1); + + string_list_append(files, name)->util = item; +} + +struct pathname_entry { + struct hashmap_entry ent; + const char *name; + struct file_item *item; +}; + +static int pathname_entry_cmp(const void *unused_cmp_data, + const struct hashmap_entry *he1, + const struct hashmap_entry *he2, + const void *name) +{ + const struct pathname_entry *e1 = + container_of(he1, const struct pathname_entry, ent); + const struct pathname_entry *e2 = + container_of(he2, const struct pathname_entry, ent); + + return strcmp(e1->name, name ? (const char *)name : e2->name); +} + +struct collection_status { + enum { FROM_WORKTREE = 0, FROM_INDEX = 1 } mode; + + const char *reference; + + unsigned skip_unseen:1; + size_t unmerged_count, binary_count; + struct string_list *files; + struct hashmap file_map; +}; + +static void collect_changes_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + struct collection_status *s = data; + struct diffstat_t stat = { 0 }; + int i; + + if (!q->nr) + return; + + compute_diffstat(options, &stat, q); + + for (i = 0; i < stat.nr; i++) { + const char *name = stat.files[i]->name; + int hash = strhash(name); + struct pathname_entry *entry; + struct file_item *file_item; + struct adddel *adddel, *other_adddel; + + entry = hashmap_get_entry_from_hash(&s->file_map, hash, name, + struct pathname_entry, ent); + if (!entry) { + if (s->skip_unseen) + continue; + + add_file_item(s->files, name); + + entry = xcalloc(sizeof(*entry), 1); + hashmap_entry_init(&entry->ent, hash); + entry->name = s->files->items[s->files->nr - 1].string; + entry->item = s->files->items[s->files->nr - 1].util; + hashmap_add(&s->file_map, &entry->ent); + } + + file_item = entry->item; + adddel = s->mode == FROM_INDEX ? + &file_item->index : &file_item->worktree; + other_adddel = s->mode == FROM_INDEX ? + &file_item->worktree : &file_item->index; + adddel->seen = 1; + adddel->add = stat.files[i]->added; + adddel->del = stat.files[i]->deleted; + if (stat.files[i]->is_binary) { + if (!other_adddel->binary) + s->binary_count++; + adddel->binary = 1; + } + if (stat.files[i]->is_unmerged) { + if (!other_adddel->unmerged) + s->unmerged_count++; + adddel->unmerged = 1; + } + } + free_diffstat_info(&stat); +} + +enum modified_files_filter { + NO_FILTER = 0, + WORKTREE_ONLY = 1, + INDEX_ONLY = 2, +}; + +static int get_modified_files(struct repository *r, + enum modified_files_filter filter, + struct prefix_item_list *files, + const struct pathspec *ps, + size_t *unmerged_count, + size_t *binary_count) +{ + struct object_id head_oid; + int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + &head_oid, NULL); + struct collection_status s = { 0 }; + int i; + + if (discard_index(r->index) < 0 || + repo_read_index_preload(r, ps, 0) < 0) + return error(_("could not read index")); + + prefix_item_list_clear(files); + s.files = &files->items; + hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0); + + for (i = 0; i < 2; i++) { + struct rev_info rev; + struct setup_revision_opt opt = { 0 }; + + if (filter == INDEX_ONLY) + s.mode = (i == 0) ? FROM_INDEX : FROM_WORKTREE; + else + s.mode = (i == 0) ? FROM_WORKTREE : FROM_INDEX; + s.skip_unseen = filter && i; + + opt.def = is_initial ? + empty_tree_oid_hex() : oid_to_hex(&head_oid); + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, &opt); + + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = collect_changes_cb; + rev.diffopt.format_callback_data = &s; + + if (ps) + copy_pathspec(&rev.prune_data, ps); + + if (s.mode == FROM_INDEX) + run_diff_index(&rev, 1); + else { + rev.diffopt.flags.ignore_dirty_submodules = 1; + run_diff_files(&rev, 0); + } + + if (ps) + clear_pathspec(&rev.prune_data); + } + hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + if (unmerged_count) + *unmerged_count = s.unmerged_count; + if (binary_count) + *binary_count = s.binary_count; + + /* While the diffs are ordered already, we ran *two* diffs... */ + string_list_sort(&files->items); + + return 0; +} + +static void render_adddel(struct strbuf *buf, + struct adddel *ad, const char *no_changes) +{ + if (ad->binary) + strbuf_addstr(buf, _("binary")); + else if (ad->seen) + strbuf_addf(buf, "+%"PRIuMAX"/-%"PRIuMAX, + (uintmax_t)ad->add, (uintmax_t)ad->del); + else + strbuf_addstr(buf, no_changes); +} + +/* filters out prefixes which have special meaning to list_and_choose() */ +static int is_valid_prefix(const char *prefix, size_t prefix_len) +{ + return prefix_len && prefix && + /* + * We expect `prefix` to be NUL terminated, therefore this + * `strcspn()` call is okay, even if it might do much more + * work than strictly necessary. + */ + strcspn(prefix, " \t\r\n,") >= prefix_len && /* separators */ + *prefix != '-' && /* deselection */ + !isdigit(*prefix) && /* selection */ + (prefix_len != 1 || + (*prefix != '*' && /* "all" wildcard */ + *prefix != '?')); /* prompt help */ +} + +struct print_file_item_data { + const char *modified_fmt, *color, *reset; + struct strbuf buf, name, index, worktree; + unsigned only_names:1; +}; + +static void print_file_item(int i, int selected, struct string_list_item *item, + void *print_file_item_data) +{ + struct file_item *c = item->util; + struct print_file_item_data *d = print_file_item_data; + const char *highlighted = NULL; + + strbuf_reset(&d->index); + strbuf_reset(&d->worktree); + strbuf_reset(&d->buf); + + /* Format the item with the prefix highlighted. */ + if (c->prefix_length > 0 && + is_valid_prefix(item->string, c->prefix_length)) { + strbuf_reset(&d->name); + strbuf_addf(&d->name, "%s%.*s%s%s", d->color, + (int)c->prefix_length, item->string, d->reset, + item->string + c->prefix_length); + highlighted = d->name.buf; + } + + if (d->only_names) { + printf("%c%2d: %s", selected ? '*' : ' ', i + 1, + highlighted ? highlighted : item->string); + return; + } + + render_adddel(&d->worktree, &c->worktree, _("nothing")); + render_adddel(&d->index, &c->index, _("unchanged")); + + strbuf_addf(&d->buf, d->modified_fmt, d->index.buf, d->worktree.buf, + highlighted ? highlighted : item->string); + + printf("%c%2d: %s", selected ? '*' : ' ', i + 1, d->buf.buf); +} + +static int run_status(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + if (get_modified_files(s->r, NO_FILTER, files, ps, NULL, NULL) < 0) + return -1; + + list(s, &files->items, NULL, &opts->list_opts); + putchar('\n'); + + return 0; +} + +static int run_update(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + int res = 0, fd; + size_t count, i; + struct lock_file index_lock; + + if (get_modified_files(s->r, WORKTREE_ONLY, files, ps, NULL, NULL) < 0) + return -1; + + if (!files->items.nr) { + putchar('\n'); + return 0; + } + + opts->prompt = N_("Update"); + count = list_and_choose(s, files, opts); + if (count <= 0) { + putchar('\n'); + return 0; + } + + fd = repo_hold_locked_index(s->r, &index_lock, LOCK_REPORT_ON_ERROR); + if (fd < 0) { + putchar('\n'); + return -1; + } + + for (i = 0; i < files->items.nr; i++) { + const char *name = files->items.items[i].string; + if (files->selected[i] && + add_file_to_index(s->r->index, name, 0) < 0) { + res = error(_("could not stage '%s'"), name); + break; + } + } + + if (!res && write_locked_index(s->r->index, &index_lock, COMMIT_LOCK) < 0) + res = error(_("could not write index")); + + if (!res) + printf(Q_("updated %d path\n", + "updated %d paths\n", count), (int)count); + + putchar('\n'); + return res; +} + +static void revert_from_diff(struct diff_queue_struct *q, + struct diff_options *opt, void *data) +{ + int i, add_flags = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + + for (i = 0; i < q->nr; i++) { + struct diff_filespec *one = q->queue[i]->one; + struct cache_entry *ce; + + if (!(one->mode && !is_null_oid(&one->oid))) { + remove_file_from_index(opt->repo->index, one->path); + printf(_("note: %s is untracked now.\n"), one->path); + } else { + ce = make_cache_entry(opt->repo->index, one->mode, + &one->oid, one->path, 0, 0); + if (!ce) + die(_("make_cache_entry failed for path '%s'"), + one->path); + add_index_entry(opt->repo->index, ce, add_flags); + } + } +} + +static int run_revert(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + int res = 0, fd; + size_t count, i, j; + + struct object_id oid; + int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &oid, + NULL); + struct lock_file index_lock; + const char **paths; + struct tree *tree; + struct diff_options diffopt = { NULL }; + + if (get_modified_files(s->r, INDEX_ONLY, files, ps, NULL, NULL) < 0) + return -1; + + if (!files->items.nr) { + putchar('\n'); + return 0; + } + + opts->prompt = N_("Revert"); + count = list_and_choose(s, files, opts); + if (count <= 0) + goto finish_revert; + + fd = repo_hold_locked_index(s->r, &index_lock, LOCK_REPORT_ON_ERROR); + if (fd < 0) { + res = -1; + goto finish_revert; + } + + if (is_initial) + oidcpy(&oid, s->r->hash_algo->empty_tree); + else { + tree = parse_tree_indirect(&oid); + if (!tree) { + res = error(_("Could not parse HEAD^{tree}")); + goto finish_revert; + } + oidcpy(&oid, &tree->object.oid); + } + + ALLOC_ARRAY(paths, count + 1); + for (i = j = 0; i < files->items.nr; i++) + if (files->selected[i]) + paths[j++] = files->items.items[i].string; + paths[j] = NULL; + + parse_pathspec(&diffopt.pathspec, 0, + PATHSPEC_PREFER_FULL | PATHSPEC_LITERAL_PATH, + NULL, paths); + + diffopt.output_format = DIFF_FORMAT_CALLBACK; + diffopt.format_callback = revert_from_diff; + diffopt.flags.override_submodule_config = 1; + diffopt.repo = s->r; + + if (do_diff_cache(&oid, &diffopt)) + res = -1; + else { + diffcore_std(&diffopt); + diff_flush(&diffopt); + } + free(paths); + clear_pathspec(&diffopt.pathspec); + + if (!res && write_locked_index(s->r->index, &index_lock, + COMMIT_LOCK) < 0) + res = -1; + else + res = repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0, 1, + NULL, NULL, NULL); + + if (!res) + printf(Q_("reverted %d path\n", + "reverted %d paths\n", count), (int)count); + +finish_revert: + putchar('\n'); + return res; +} + +static int get_untracked_files(struct repository *r, + struct prefix_item_list *files, + const struct pathspec *ps) +{ + struct dir_struct dir = { 0 }; + size_t i; + struct strbuf buf = STRBUF_INIT; + + if (repo_read_index(r) < 0) + return error(_("could not read index")); + + prefix_item_list_clear(files); + setup_standard_excludes(&dir); + add_pattern_list(&dir, EXC_CMDL, "--exclude option"); + fill_directory(&dir, r->index, ps); + + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + + if (index_name_is_other(r->index, ent->name, ent->len)) { + strbuf_reset(&buf); + strbuf_add(&buf, ent->name, ent->len); + add_file_item(&files->items, buf.buf); + } + } + + strbuf_release(&buf); + return 0; +} + +static int run_add_untracked(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + struct print_file_item_data *d = opts->list_opts.print_item_data; + int res = 0, fd; + size_t count, i; + struct lock_file index_lock; + + if (get_untracked_files(s->r, files, ps) < 0) + return -1; + + if (!files->items.nr) { + printf(_("No untracked files.\n")); + goto finish_add_untracked; + } + + opts->prompt = N_("Add untracked"); + d->only_names = 1; + count = list_and_choose(s, files, opts); + d->only_names = 0; + if (count <= 0) + goto finish_add_untracked; + + fd = repo_hold_locked_index(s->r, &index_lock, LOCK_REPORT_ON_ERROR); + if (fd < 0) { + res = -1; + goto finish_add_untracked; + } + + for (i = 0; i < files->items.nr; i++) { + const char *name = files->items.items[i].string; + if (files->selected[i] && + add_file_to_index(s->r->index, name, 0) < 0) { + res = error(_("could not stage '%s'"), name); + break; + } + } + + if (!res && + write_locked_index(s->r->index, &index_lock, COMMIT_LOCK) < 0) + res = error(_("could not write index")); + + if (!res) + printf(Q_("added %d path\n", + "added %d paths\n", count), (int)count); + +finish_add_untracked: + putchar('\n'); + return res; +} + +static int run_patch(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + int res = 0; + ssize_t count, i, j; + size_t unmerged_count = 0, binary_count = 0; + + if (get_modified_files(s->r, WORKTREE_ONLY, files, ps, + &unmerged_count, &binary_count) < 0) + return -1; + + if (unmerged_count || binary_count) { + for (i = j = 0; i < files->items.nr; i++) { + struct file_item *item = files->items.items[i].util; + + if (item->index.binary || item->worktree.binary) { + free(item); + free(files->items.items[i].string); + } else if (item->index.unmerged || + item->worktree.unmerged) { + color_fprintf_ln(stderr, s->error_color, + _("ignoring unmerged: %s"), + files->items.items[i].string); + free(item); + free(files->items.items[i].string); + } else + files->items.items[j++] = files->items.items[i]; + } + files->items.nr = j; + } + + if (!files->items.nr) { + if (binary_count) + fprintf(stderr, _("Only binary files changed.\n")); + else + fprintf(stderr, _("No changes.\n")); + return 0; + } + + opts->prompt = N_("Patch update"); + count = list_and_choose(s, files, opts); + if (count >= 0) { + struct argv_array args = ARGV_ARRAY_INIT; + + argv_array_pushl(&args, "git", "add--interactive", "--patch", + "--", NULL); + for (i = 0; i < files->items.nr; i++) + if (files->selected[i]) + argv_array_push(&args, + files->items.items[i].string); + res = run_command_v_opt(args.argv, 0); + argv_array_clear(&args); + } + + return res; +} + +static int run_diff(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts) +{ + int res = 0; + ssize_t count, i; + + struct object_id oid; + int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &oid, + NULL); + if (get_modified_files(s->r, INDEX_ONLY, files, ps, NULL, NULL) < 0) + return -1; + + if (!files->items.nr) { + putchar('\n'); + return 0; + } + + opts->prompt = N_("Review diff"); + opts->flags = IMMEDIATE; + count = list_and_choose(s, files, opts); + opts->flags = 0; + if (count >= 0) { + struct argv_array args = ARGV_ARRAY_INIT; + + argv_array_pushl(&args, "git", "diff", "-p", "--cached", + oid_to_hex(!is_initial ? &oid : + s->r->hash_algo->empty_tree), + "--", NULL); + for (i = 0; i < files->items.nr; i++) + if (files->selected[i]) + argv_array_push(&args, + files->items.items[i].string); + res = run_command_v_opt(args.argv, 0); + argv_array_clear(&args); + } + + putchar('\n'); + return res; +} + +static int run_help(struct add_i_state *s, const struct pathspec *unused_ps, + struct prefix_item_list *unused_files, + struct list_and_choose_options *unused_opts) +{ + color_fprintf_ln(stdout, s->help_color, "status - %s", + _("show paths with changes")); + color_fprintf_ln(stdout, s->help_color, "update - %s", + _("add working tree state to the staged set of changes")); + color_fprintf_ln(stdout, s->help_color, "revert - %s", + _("revert staged set of changes back to the HEAD version")); + color_fprintf_ln(stdout, s->help_color, "patch - %s", + _("pick hunks and update selectively")); + color_fprintf_ln(stdout, s->help_color, "diff - %s", + _("view diff between HEAD and index")); + color_fprintf_ln(stdout, s->help_color, "add untracked - %s", + _("add contents of untracked files to the staged set of changes")); + + return 0; +} + +static void choose_prompt_help(struct add_i_state *s) +{ + color_fprintf_ln(stdout, s->help_color, "%s", + _("Prompt help:")); + color_fprintf_ln(stdout, s->help_color, "1 - %s", + _("select a single item")); + color_fprintf_ln(stdout, s->help_color, "3-5 - %s", + _("select a range of items")); + color_fprintf_ln(stdout, s->help_color, "2-3,6-9 - %s", + _("select multiple ranges")); + color_fprintf_ln(stdout, s->help_color, "foo - %s", + _("select item based on unique prefix")); + color_fprintf_ln(stdout, s->help_color, "-... - %s", + _("unselect specified items")); + color_fprintf_ln(stdout, s->help_color, "* - %s", + _("choose all items")); + color_fprintf_ln(stdout, s->help_color, " - %s", + _("(empty) finish selecting")); +} + +typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps, + struct prefix_item_list *files, + struct list_and_choose_options *opts); + +struct command_item { + size_t prefix_length; + command_t command; +}; + +struct print_command_item_data { + const char *color, *reset; +}; + +static void print_command_item(int i, int selected, + struct string_list_item *item, + void *print_command_item_data) +{ + struct print_command_item_data *d = print_command_item_data; + struct command_item *util = item->util; + + if (!util->prefix_length || + !is_valid_prefix(item->string, util->prefix_length)) + printf(" %2d: %s", i + 1, item->string); + else + printf(" %2d: %s%.*s%s%s", i + 1, + d->color, (int)util->prefix_length, item->string, + d->reset, item->string + util->prefix_length); +} + +static void command_prompt_help(struct add_i_state *s) +{ + const char *help_color = s->help_color; + color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:")); + color_fprintf_ln(stdout, help_color, "1 - %s", + _("select a numbered item")); + color_fprintf_ln(stdout, help_color, "foo - %s", + _("select item based on unique prefix")); + color_fprintf_ln(stdout, help_color, " - %s", + _("(empty) select nothing")); +} + +int run_add_i(struct repository *r, const struct pathspec *ps) +{ + struct add_i_state s = { NULL }; + struct print_command_item_data data = { "[", "]" }; + struct list_and_choose_options main_loop_opts = { + { 4, N_("*** Commands ***"), print_command_item, &data }, + N_("What now"), SINGLETON | IMMEDIATE, command_prompt_help + }; + struct { + const char *string; + command_t command; + } command_list[] = { + { "status", run_status }, + { "update", run_update }, + { "revert", run_revert }, + { "add untracked", run_add_untracked }, + { "patch", run_patch }, + { "diff", run_diff }, + { "quit", NULL }, + { "help", run_help }, + }; + struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT; + + struct print_file_item_data print_file_item_data = { + "%12s %12s %s", NULL, NULL, + STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + }; + struct list_and_choose_options opts = { + { 0, NULL, print_file_item, &print_file_item_data }, + NULL, 0, choose_prompt_help + }; + struct strbuf header = STRBUF_INIT; + struct prefix_item_list files = PREFIX_ITEM_LIST_INIT; + ssize_t i; + int res = 0; + + for (i = 0; i < ARRAY_SIZE(command_list); i++) { + struct command_item *util = xcalloc(sizeof(*util), 1); + util->command = command_list[i].command; + string_list_append(&commands.items, command_list[i].string) + ->util = util; + } + + init_add_i_state(&s, r); + + /* + * When color was asked for, use the prompt color for + * highlighting, otherwise use square brackets. + */ + if (s.use_color) { + data.color = s.prompt_color; + data.reset = s.reset_color; + } + print_file_item_data.color = data.color; + print_file_item_data.reset = data.reset; + + strbuf_addstr(&header, " "); + strbuf_addf(&header, print_file_item_data.modified_fmt, + _("staged"), _("unstaged"), _("path")); + opts.list_opts.header = header.buf; + + if (discard_index(r->index) < 0 || + repo_read_index(r) < 0 || + repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, + NULL, NULL, NULL) < 0) + warning(_("could not refresh index")); + + res = run_status(&s, ps, &files, &opts); + + for (;;) { + struct command_item *util; + + i = list_and_choose(&s, &commands, &main_loop_opts); + if (i < 0 || i >= commands.items.nr) + util = NULL; + else + util = commands.items.items[i].util; + + if (i == LIST_AND_CHOOSE_QUIT || (util && !util->command)) { + printf(_("Bye.\n")); + res = 0; + break; + } + + if (util) + res = util->command(&s, ps, &files, &opts); + } + + prefix_item_list_clear(&files); + strbuf_release(&print_file_item_data.buf); + strbuf_release(&print_file_item_data.name); + strbuf_release(&print_file_item_data.index); + strbuf_release(&print_file_item_data.worktree); + strbuf_release(&header); + prefix_item_list_clear(&commands); + + return res; +} diff --git a/add-interactive.h b/add-interactive.h new file mode 100644 index 0000000000..7043b8741d --- /dev/null +++ b/add-interactive.h @@ -0,0 +1,8 @@ +#ifndef ADD_INTERACTIVE_H +#define ADD_INTERACTIVE_H + +struct repository; +struct pathspec; +int run_add_i(struct repository *r, const struct pathspec *ps); + +#endif @@ -30,6 +30,7 @@ int advice_waiting_for_editor = 1; int advice_graft_file_deprecated = 1; int advice_checkout_ambiguous_remote_branch_name = 1; int advice_nested_tag = 1; +int advice_submodule_alternate_error_strategy_die = 1; static int advice_use_color = -1; static char advice_colors[][COLOR_MAXLEN] = { @@ -89,6 +90,7 @@ static struct { { "graftFileDeprecated", &advice_graft_file_deprecated }, { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name }, { "nestedTag", &advice_nested_tag }, + { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die }, /* make this an alias for backward compatibility */ { "pushNonFastForward", &advice_push_update_rejected } @@ -30,6 +30,7 @@ extern int advice_waiting_for_editor; extern int advice_graft_file_deprecated; extern int advice_checkout_ambiguous_remote_branch_name; extern int advice_nested_tag; +extern int advice_submodule_alternate_error_strategy_die; int git_default_advice_config(const char *var, const char *value); __attribute__((format (printf, 1, 2))) @@ -450,7 +450,7 @@ static char *find_name_gnu(struct strbuf *root, /* * Proposed "new-style" GNU patch/diff format; see - * https://public-inbox.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/ + * https://lore.kernel.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/ */ if (unquote_c_style(&name, line, NULL)) { strbuf_release(&name); diff --git a/archive-zip.c b/archive-zip.c index 4d66b5be6e..313c6b9c46 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -603,18 +603,18 @@ static void write_zip_trailer(const struct object_id *oid) static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time) { time_t time; - struct tm *t; + struct tm tm; if (date_overflows(*timestamp)) die(_("timestamp too large for this system: %"PRItime), *timestamp); time = (time_t)*timestamp; - t = localtime(&time); + localtime_r(&time, &tm); *timestamp = time; - *dos_date = t->tm_mday + (t->tm_mon + 1) * 32 + - (t->tm_year + 1900 - 1980) * 512; - *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; + *dos_date = tm.tm_mday + (tm.tm_mon + 1) * 32 + + (tm.tm_year + 1900 - 1980) * 512; + *dos_time = tm.tm_sec / 2 + tm.tm_min * 32 + tm.tm_hour * 2048; } static int archive_zip_config(const char *var, const char *value, void *data) diff --git a/argv-array.c b/argv-array.c index f352ea9357..61ef8c0dfd 100644 --- a/argv-array.c +++ b/argv-array.c @@ -46,7 +46,7 @@ void argv_array_pushl(struct argv_array *array, ...) const char *arg; va_start(ap, array); - while((arg = va_arg(ap, const char *))) + while ((arg = va_arg(ap, const char *))) argv_array_push(array, arg); va_end(ap); } diff --git a/builtin/add.c b/builtin/add.c index dd18e5c9b6..d4686d5218 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -20,6 +20,7 @@ #include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -185,6 +186,16 @@ int run_add_interactive(const char *revision, const char *patch_mode, { int status, i; struct argv_array argv = ARGV_ARRAY_INIT; + int use_builtin_add_i = + git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); + + if (!patch_mode) { + if (use_builtin_add_i < 0) + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i); + if (use_builtin_add_i == 1) + return !!run_add_i(the_repository, pathspec); + } argv_array_push(&argv, "add--interactive"); if (patch_mode) @@ -319,6 +330,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors = git_config_bool(var, value); return 0; } + return git_default_config(var, value, cb); } diff --git a/builtin/clone.c b/builtin/clone.c index 40c0bf25be..6dee265cc9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -789,7 +789,7 @@ static int checkout(int submodule_progress) if (!err && (option_recurse_submodules.nr > 0)) { struct argv_array args = ARGV_ARRAY_INIT; - argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL); if (option_shallow_submodules == 1) argv_array_push(&args, "--depth=1"); diff --git a/builtin/commit.c b/builtin/commit.c index 294dc574cd..2db2ad0de4 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -107,9 +107,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ -static int no_post_rewrite, allow_empty_message; +static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; -static char *sign_commit; +static char *sign_commit, *pathspec_from_file; /* * The default commit message cleanup mode will remove the lines @@ -343,6 +343,23 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix PATHSPEC_PREFER_FULL, prefix, argv); + if (pathspec_from_file) { + if (interactive) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + die(_("No paths with --include/--only does not make sense.")); + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); @@ -1198,8 +1215,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend && !allow_empty))) - die(_("No paths with --include/--only does not make sense.")); cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1513,6 +1528,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, diff --git a/builtin/fetch.c b/builtin/fetch.c index 46ce7c2710..f8765b385b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -957,18 +957,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, kind = ""; what = ""; } - else if (starts_with(rm->name, "refs/heads/")) { + else if (skip_prefix(rm->name, "refs/heads/", &what)) kind = "branch"; - what = rm->name + 11; - } - else if (starts_with(rm->name, "refs/tags/")) { + else if (skip_prefix(rm->name, "refs/tags/", &what)) kind = "tag"; - what = rm->name + 10; - } - else if (starts_with(rm->name, "refs/remotes/")) { + else if (skip_prefix(rm->name, "refs/remotes/", &what)) kind = "remote-tracking branch"; - what = rm->name + 13; - } else { kind = ""; what = rm->name; diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index a4615587fd..05a92c59d8 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -106,7 +106,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents) int i, len = strlen(line); struct origin_data *origin_data; char *src; - const char *origin; + const char *origin, *tag_name; struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; @@ -162,14 +162,13 @@ static int handle_line(char *line, struct merge_parents *merge_parents) if (pulling_head) { origin = src; src_data->head_status |= 1; - } else if (starts_with(line, "branch ")) { + } else if (skip_prefix(line, "branch ", &origin)) { origin_data->is_local_branch = 1; - origin = line + 7; string_list_append(&src_data->branch, origin); src_data->head_status |= 2; - } else if (starts_with(line, "tag ")) { + } else if (skip_prefix(line, "tag ", &tag_name)) { origin = line; - string_list_append(&src_data->tag, origin + 4); + string_list_append(&src_data->tag, tag_name); src_data->head_status |= 2; } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { string_list_append(&src_data->r_branch, origin); @@ -495,6 +494,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) enum object_type type; unsigned long size, len; char *buf = read_object_file(oid, &type, &size); + struct signature_check sigc = { 0 }; struct strbuf sig = STRBUF_INIT; if (!buf || type != OBJ_TAG) @@ -503,10 +503,12 @@ static void fmt_merge_msg_sigs(struct strbuf *out) if (size == len) ; /* merely annotated */ - else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { - if (!sig.len) - strbuf_addstr(&sig, "gpg verification failed.\n"); - } + else if (!check_signature(buf, len, buf + len, size - len, + &sigc)) { + strbuf_addstr(&sig, sigc.gpg_output); + signature_check_clear(&sigc); + } else + strbuf_addstr(&sig, "gpg verification failed.\n"); if (!tag_number++) { fmt_tag_signature(&tagbuf, &sig, buf, len); diff --git a/builtin/log.c b/builtin/log.c index bf904e887f..8c664067ca 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1111,6 +1111,25 @@ do_pp: strbuf_release(&subject_sb); } +static int get_notes_refs(struct string_list_item *item, void *arg) +{ + argv_array_pushf(arg, "--notes=%s", item->string); + return 0; +} + +static void get_notes_args(struct argv_array *arg, struct rev_info *rev) +{ + if (!rev->show_notes) { + argv_array_push(arg, "--no-notes"); + } else if (rev->notes_opt.use_default_notes > 0 || + (rev->notes_opt.use_default_notes == -1 && + !rev->notes_opt.extra_notes_refs.nr)) { + argv_array_push(arg, "--notes"); + } else { + for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, int nr, struct commit **list, @@ -1183,13 +1202,16 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, * can be added later if deemed desirable. */ struct diff_options opts; + struct argv_array other_arg = ARGV_ARRAY_INIT; diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); + get_notes_args(&other_arg, rev); show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts); + rev->creation_factor, 1, &opts, &other_arg); + argv_array_clear(&other_arg); } } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index b0f0776947..e55a4f04ee 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -161,10 +161,10 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) { if (shorten_unambiguous) refname = shorten_unambiguous_ref(refname, 0); - else if (starts_with(refname, "refs/heads/")) - refname = refname + 11; - else if (starts_with(refname, "refs/")) - refname = refname + 5; + else if (skip_prefix(refname, "refs/heads/", &refname)) + ; /* refname already advanced */ + else + skip_prefix(refname, "refs/", &refname); return refname; } diff --git a/builtin/push.c b/builtin/push.c index 843f5b22a2..6dbf0f0bb7 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -64,6 +64,7 @@ static struct string_list push_options_config = STRING_LIST_INIT_DUP; static const char *map_refspec(const char *ref, struct remote *remote, struct ref *local_refs) { + const char *branch_name; struct ref *matched = NULL; /* Does "ref" uniquely name our ref? */ @@ -84,8 +85,8 @@ static const char *map_refspec(const char *ref, } if (push_default == PUSH_DEFAULT_UPSTREAM && - starts_with(matched->name, "refs/heads/")) { - struct branch *branch = branch_get(matched->name + 11); + skip_prefix(matched->name, "refs/heads/", &branch_name)) { + struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s:%s", diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 9202e75544..98acf3533e 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -15,12 +15,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) { int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; + struct argv_array other_arg = ARGV_ARRAY_INIT; int simple_color = -1; struct option range_diff_options[] = { OPT_INTEGER(0, "creation-factor", &creation_factor, N_("Percentage by which creation is weighted")), OPT_BOOL(0, "no-dual-color", &simple_color, N_("use simple diff colors")), + OPT_PASSTHRU_ARGV(0, "notes", &other_arg, + N_("notes"), N_("passed to 'git log'"), + PARSE_OPT_OPTARG), OPT_END() }; struct option *options; @@ -78,7 +82,7 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) FREE_AND_NULL(options); res = show_range_diff(range1.buf, range2.buf, creation_factor, - simple_color < 1, &diffopt); + simple_color < 1, &diffopt, &other_arg); strbuf_release(&range1); strbuf_release(&range2); diff --git a/builtin/rebase.c b/builtin/rebase.c index 51980ab63d..ddf33bc9d4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,8 +79,11 @@ struct rebase_options { int allow_rerere_autoupdate; int keep_empty; int autosquash; + int ignore_whitespace; char *gpg_sign_opt; int autostash; + int committer_date_is_author_date; + int ignore_date; char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; @@ -99,6 +102,7 @@ struct rebase_options { static struct replay_opts get_replay_opts(const struct rebase_options *opts) { + struct strbuf strategy_buf = STRBUF_INIT; struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -112,10 +116,25 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_empty_message = opts->allow_empty_message; replay.verbose = opts->flags & REBASE_VERBOSE; replay.reschedule_failed_exec = opts->reschedule_failed_exec; + replay.committer_date_is_author_date = + opts->committer_date_is_author_date; + replay.ignore_date = opts->ignore_date; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); replay.strategy = opts->strategy; + if (opts->strategy_opts) - parse_strategy_opts(&replay, opts->strategy_opts); + strbuf_addstr(&strategy_buf, opts->strategy_opts); + if (opts->ignore_whitespace) + strbuf_addstr(&strategy_buf, " --ignore-space-change"); + if (strategy_buf.len) + parse_strategy_opts(&replay, strategy_buf.buf); + + strbuf_release(&strategy_buf); + + if (opts->squash_onto) { + oidcpy(&replay.squash_onto, opts->squash_onto); + replay.have_squash_onto = 1; + } return replay; } @@ -512,6 +531,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_rebase_interactive_usage, PARSE_OPT_KEEP_ARGV0); + opts.strategy_opts = xstrdup_or_null(opts.strategy_opts); + if (!is_null_oid(&squash_onto)) opts.squash_onto = &squash_onto; @@ -965,6 +986,12 @@ static int run_am(struct rebase_options *opts) am.git_cmd = 1; argv_array_push(&am.args, "am"); + if (opts->ignore_whitespace) + argv_array_push(&am.args, "--ignore-whitespace"); + if (opts->committer_date_is_author_date) + argv_array_push(&opts->git_am_opts, "--committer-date-is-author-date"); + if (opts->ignore_date) + argv_array_push(&opts->git_am_opts, "--ignore-date"); if (opts->action && !strcmp("continue", opts->action)) { argv_array_push(&am.args, "--resolved"); argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); @@ -1432,16 +1459,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), - OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts, - NULL, N_("passed to 'git am'"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date", - &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), + OPT_BOOL(0, "committer-date-is-author-date", + &options.committer_date_is_author_date, + N_("make committer date match author date")), + OPT_BOOL(0, "reset-author-date", &options.ignore_date, + N_("ignore author date and use current date")), + OPT_HIDDEN_BOOL(0, "ignore-date", &options.ignore_date, + N_("synonym of --reset-author-date")), OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"), N_("passed to 'git apply'"), 0), + OPT_BOOL(0, "ignore-whitespace", &options.ignore_whitespace, + N_("ignore changes in whitespace")), OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts, N_("action"), N_("passed to 'git apply'"), 0), OPT_BIT('f', "force-rebase", &options.flags, @@ -1714,11 +1742,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } + if (options.committer_date_is_author_date || + options.ignore_date) + options.flags |= REBASE_FORCE; + for (i = 0; i < options.git_am_opts.argc; i++) { const char *option = options.git_am_opts.argv[i], *p; - if (!strcmp(option, "--committer-date-is-author-date") || - !strcmp(option, "--ignore-date") || - !strcmp(option, "--whitespace=fix") || + if (!strcmp(option, "--whitespace=fix") || !strcmp(option, "--whitespace=strip")) options.flags |= REBASE_FORCE; else if (skip_prefix(option, "-C", &p)) { diff --git a/builtin/reset.c b/builtin/reset.c index fdd572168b..18228c312e 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -30,8 +30,9 @@ static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), - N_("git reset [-q] [<tree-ish>] [--] <paths>..."), - N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), + N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."), + N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"), + N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"), NULL }; @@ -284,8 +285,8 @@ static int git_reset_config(const char *var, const char *value, void *cb) int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0, unborn; - const char *rev; + int patch_mode = 0, pathspec_file_nul = 0, unborn; + const char *rev, *pathspec_from_file = NULL; struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; @@ -306,6 +307,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; @@ -316,11 +319,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ oidcpy(&oid, the_hash_algo->empty_tree); - } else if (!pathspec.nr) { + } else if (!pathspec.nr && !patch_mode) { struct commit *commit; if (get_oid_committish(rev, &oid)) die(_("Failed to resolve '%s' as a valid revision."), rev); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 85ce2095bf..7a00da8203 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -803,6 +803,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *work_tree = get_git_work_tree(); if (work_tree) puts(work_tree); + else + die("this operation must be run in a work tree"); continue; } if (!strcmp(arg, "--show-superproject-working-tree")) { diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2c2395a620..c72931ecd7 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -19,6 +19,8 @@ #include "diffcore.h" #include "diff.h" #include "object-store.h" +#include "dir.h" +#include "advice.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -802,7 +804,8 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, path, NULL); git_config(git_diff_basic_config, NULL); - repo_init_revisions(the_repository, &rev, prefix); + + repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = 0; diff_files_args.argc = setup_revisions(diff_files_args.argc, diff_files_args.argv, @@ -1268,6 +1271,13 @@ struct submodule_alternate_setup { #define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +static const char alternate_error_advice[] = N_( +"An alternate computed from a superproject's alternate is invalid.\n" +"To allow Git to clone without an alternate in such a case, set\n" +"submodule.alternateErrorStrategy to 'info' or, equivalently, clone with\n" +"'--reference-if-able' instead of '--reference'." +); + static int add_possible_reference_from_superproject( struct object_directory *odb, void *sas_cb) { @@ -1299,6 +1309,8 @@ static int add_possible_reference_from_superproject( } else { switch (sas->error_mode) { case SUBMODULE_ALTERNATE_ERROR_DIE: + if (advice_submodule_alternate_error_strategy_die) + advise(_(alternate_error_advice)); die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_INFO: @@ -1359,7 +1371,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) char *p, *path = NULL, *sm_gitdir; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; - int dissociate = 0; + int dissociate = 0, require_init = 0; char *sm_alternate = NULL, *error_strategy = NULL; struct option module_clone_options[] = { @@ -1386,6 +1398,8 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), OPT_BOOL(0, "progress", &progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &require_init, + N_("disallow cloning into non-empty directory")), OPT_END() }; @@ -1413,6 +1427,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) } else path = xstrdup(path); + if (validate_submodule_git_dir(sm_gitdir, name) < 0) + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); @@ -1424,6 +1442,8 @@ static int module_clone(int argc, const char **argv, const char *prefix) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { + if (require_init && !access(path, X_OK) && !is_empty_dir(path)) + die(_("directory not empty: '%s'"), path); if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); strbuf_addf(&sb, "%s/index", sm_gitdir); @@ -1478,6 +1498,8 @@ static void determine_submodule_update_strategy(struct repository *r, die(_("Invalid update mode '%s' configured for submodule path '%s'"), val, path); } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) + BUG("how did we read update = !command from .gitmodules?"); out->type = sub->update_strategy.type; out->command = sub->update_strategy.command; } else @@ -1536,6 +1558,7 @@ struct submodule_update_clone { int recommend_shallow; struct string_list references; int dissociate; + unsigned require_init; const char *depth; const char *recursive_prefix; const char *prefix; @@ -1554,7 +1577,7 @@ struct submodule_update_clone { int max_jobs; }; #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ - SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ + SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \ NULL, NULL, NULL, \ NULL, 0, 0, 0, NULL, 0, 0, 1} @@ -1681,6 +1704,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); if (suc->recommend_shallow && sub->recommend_shallow == 1) argv_array_push(&child->args, "--depth=1"); + if (suc->require_init) + argv_array_push(&child->args, "--require-init"); argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); argv_array_pushl(&child->args, "--url", url, NULL); @@ -1870,6 +1895,8 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), OPT_BOOL(0, "progress", &suc.progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &suc.require_init, + N_("disallow cloning into non-empty directory")), OPT_END() }; diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index a87a4bfd2c..9100964667 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -24,6 +24,7 @@ static off_t consumed_bytes; static off_t max_input_size; static git_hash_ctx ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct progress *progress; /* * When running under --strict mode, objects whose reachability are @@ -92,6 +93,7 @@ static void use(int bytes) consumed_bytes += bytes; if (max_input_size && consumed_bytes > max_input_size) die(_("pack exceeds maximum allowed size")); + display_throughput(progress, consumed_bytes); } static void *get_data(unsigned long size) @@ -484,7 +486,6 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); nr_objects = ntohl(hdr->hdr_entries); diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 4e64a19112..cd59855d73 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -42,14 +42,17 @@ osx-clang|osx-gcc) brew link --force gettext brew cask install perforce || { # Update the definitions and try again - git -C "$(brew --repository)"/Library/Taps/homebrew/homebrew-cask pull && + cask_repo="$(brew --repository)"/Library/Taps/homebrew/homebrew-cask && + git -C "$cask_repo" pull --no-stat && brew cask install perforce } || brew install caskroom/cask/perforce case "$jobname" in osx-gcc) - brew link gcc || - brew link gcc@8 + brew install gcc@9 + # Just in case the image is updated to contain gcc@9 + # pre-installed but not linked. + brew link gcc@9 ;; esac ;; @@ -131,7 +131,6 @@ then echo "$SYSTEM_TASKDEFINITIONSURI$SYSTEM_TEAMPROJECT/_build/results?buildId=$1" } - BREW_INSTALL_PACKAGES=gcc@8 export GIT_PROVE_OPTS="--timer --jobs 10 --state=failed,slow,save" export GIT_TEST_OPTS="--verbose-log -x --write-junit-xml" MAKEFLAGS="$MAKEFLAGS --jobs=10" @@ -156,7 +155,7 @@ fi export DEVELOPER=1 export DEFAULT_TEST_TARGET=prove -export GIT_TEST_CLONE_2GB=YesPlease +export GIT_TEST_CLONE_2GB=true case "$jobname" in linux-clang|linux-gcc) @@ -182,7 +181,7 @@ linux-clang|linux-gcc) osx-clang|osx-gcc) if [ "$jobname" = osx-gcc ] then - export CC=gcc-8 + export CC=gcc-9 fi # t9810 occasionally fails on Travis CI OS X diff --git a/commit-graph.c b/commit-graph.c index 79bc5641cb..e771394aff 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -1100,7 +1100,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) struct commit_list *list = NULL; if (ctx->report_progress) - ctx->progress = start_progress( + ctx->progress = start_delayed_progress( _("Computing commit graph generation numbers"), ctx->commits.nr); for (i = 0; i < ctx->commits.nr; i++) { diff --git a/compat/mingw.c b/compat/mingw.c index 2f4654c968..76ac8713d2 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -114,6 +114,7 @@ int err_win_to_posix(DWORD winerr) case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break; case ERROR_SHARING_VIOLATION: error = EACCES; break; case ERROR_STACK_OVERFLOW: error = ENOMEM; break; + case ERROR_SUCCESS: BUG("err_win_to_posix() called without an error!"); case ERROR_SWAPERROR: error = ENOENT; break; case ERROR_TOO_MANY_MODULES: error = EMFILE; break; case ERROR_TOO_MANY_OPEN_FILES: error = EMFILE; break; @@ -212,6 +213,7 @@ enum hide_dotfiles_type { HIDE_DOTFILES_DOTGITONLY }; +static int core_restrict_inherited_handles = -1; static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; static char *unset_environment_variables; @@ -231,6 +233,15 @@ int mingw_core_config(const char *var, const char *value, void *cb) return 0; } + if (!strcmp(var, "core.restrictinheritedhandles")) { + if (value && !strcasecmp(value, "auto")) + core_restrict_inherited_handles = -1; + else + core_restrict_inherited_handles = + git_config_bool(var, value); + return 0; + } + return 0; } @@ -392,6 +403,12 @@ int mingw_mkdir(const char *path, int mode) { int ret; wchar_t wpath[MAX_PATH]; + + if (!is_valid_win32_path(path)) { + errno = EINVAL; + return -1; + } + if (xutftowcs_path(wpath, path) < 0) return -1; ret = _wmkdir(wpath); @@ -465,7 +482,7 @@ int mingw_open (const char *filename, int oflags, ...) typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...); va_list args; unsigned mode; - int fd; + int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); wchar_t wfilename[MAX_PATH]; open_fn_t open_fn; @@ -473,6 +490,11 @@ int mingw_open (const char *filename, int oflags, ...) mode = va_arg(args, int); va_end(args); + if (!is_valid_win32_path(filename)) { + errno = create ? EINVAL : ENOENT; + return -1; + } + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; @@ -539,6 +561,11 @@ FILE *mingw_fopen (const char *filename, const char *otype) int hide = needs_hiding(filename); FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; + if (!is_valid_win32_path(filename)) { + int create = otype && strchr(otype, 'w'); + errno = create ? EINVAL : ENOENT; + return NULL; + } if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; if (xutftowcs_path(wfilename, filename) < 0 || @@ -561,6 +588,11 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) int hide = needs_hiding(filename); FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; + if (!is_valid_win32_path(filename)) { + int create = otype && strchr(otype, 'w'); + errno = create ? EINVAL : ENOENT; + return NULL; + } if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; if (xutftowcs_path(wfilename, filename) < 0 || @@ -986,16 +1018,16 @@ int pipe(int filedes[2]) struct tm *gmtime_r(const time_t *timep, struct tm *result) { - /* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */ - memcpy(result, gmtime(timep), sizeof(struct tm)); - return result; + if (gmtime_s(result, timep) == 0) + return result; + return NULL; } struct tm *localtime_r(const time_t *timep, struct tm *result) { - /* localtime() in MSVCRT.DLL is thread-safe, but not reentrant */ - memcpy(result, localtime(timep), sizeof(struct tm)); - return result; + if (localtime_s(result, timep) == 0) + return result; + return NULL; } char *mingw_getcwd(char *pointer, int len) @@ -1054,7 +1086,7 @@ static const char *quote_arg_msvc(const char *arg) p++; len++; } - if (*p == '"') + if (*p == '"' || !*p) n += count*2 + 1; continue; } @@ -1076,16 +1108,19 @@ static const char *quote_arg_msvc(const char *arg) count++; *d++ = *arg++; } - if (*arg == '"') { + if (*arg == '"' || !*arg) { while (count-- > 0) *d++ = '\\'; + /* don't escape the surrounding end quote */ + if (!*arg) + break; *d++ = '\\'; } } *d++ = *arg++; } *d++ = '"'; - *d++ = 0; + *d++ = '\0'; return q; } @@ -1098,13 +1133,14 @@ static const char *quote_arg_msys2(const char *arg) for (p = arg; *p; p++) { int ws = isspace(*p); - if (!ws && *p != '\\' && *p != '"' && *p != '{') + if (!ws && *p != '\\' && *p != '"' && *p != '{' && *p != '\'' && + *p != '?' && *p != '*' && *p != '~') continue; if (!buf.len) strbuf_addch(&buf, '"'); if (p != p2) strbuf_add(&buf, p2, p - p2); - if (!ws && *p != '{') + if (*p == '\\' || *p == '"') strbuf_addch(&buf, '\\'); p2 = p; } @@ -1114,7 +1150,7 @@ static const char *quote_arg_msys2(const char *arg) else if (!buf.len) return arg; else - strbuf_add(&buf, p2, p - p2), + strbuf_add(&buf, p2, p - p2); strbuf_addch(&buf, '"'); return strbuf_detach(&buf, 0); @@ -1371,7 +1407,10 @@ static inline int match_last_path_component(const char *path, size_t *len, static int is_msys2_sh(const char *cmd) { - if (cmd && !strcmp(cmd, "sh")) { + if (!cmd) + return 0; + + if (!strcmp(cmd, "sh")) { static int ret = -1; char *p; @@ -1391,6 +1430,16 @@ static int is_msys2_sh(const char *cmd) } return ret; } + + if (ends_with(cmd, "\\sh.exe")) { + static char *sh; + + if (!sh) + sh = path_lookup("sh", 0); + + return !fspathcmp(cmd, sh); + } + return 0; } @@ -1398,15 +1447,34 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen const char *dir, int prepend_cmd, int fhin, int fhout, int fherr) { - STARTUPINFOW si; + static int restrict_handle_inheritance = -1; + STARTUPINFOEXW si; PROCESS_INFORMATION pi; + LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL; + HANDLE stdhandles[3]; + DWORD stdhandles_count = 0; + SIZE_T size; struct strbuf args; wchar_t wcmd[MAX_PATH], wdir[MAX_PATH], *wargs, *wenvblk = NULL; unsigned flags = CREATE_UNICODE_ENVIRONMENT; BOOL ret; HANDLE cons; const char *(*quote_arg)(const char *arg) = - is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc; + is_msys2_sh(cmd ? cmd : *argv) ? + quote_arg_msys2 : quote_arg_msvc; + + /* Make sure to override previous errors, if any */ + errno = 0; + + if (restrict_handle_inheritance < 0) + restrict_handle_inheritance = core_restrict_inherited_handles; + /* + * The following code to restrict which handles are inherited seems + * to work properly only on Windows 7 and later, so let's disable it + * on Windows Vista and 2008. + */ + if (restrict_handle_inheritance < 0) + restrict_handle_inheritance = GetVersion() >> 16 >= 7601; do_unset_environment_variables(); @@ -1435,11 +1503,23 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen CloseHandle(cons); } memset(&si, 0, sizeof(si)); - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = winansi_get_osfhandle(fhin); - si.hStdOutput = winansi_get_osfhandle(fhout); - si.hStdError = winansi_get_osfhandle(fherr); + si.StartupInfo.cb = sizeof(si); + si.StartupInfo.hStdInput = winansi_get_osfhandle(fhin); + si.StartupInfo.hStdOutput = winansi_get_osfhandle(fhout); + si.StartupInfo.hStdError = winansi_get_osfhandle(fherr); + + /* The list of handles cannot contain duplicates */ + if (si.StartupInfo.hStdInput != INVALID_HANDLE_VALUE) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdInput; + if (si.StartupInfo.hStdOutput != INVALID_HANDLE_VALUE && + si.StartupInfo.hStdOutput != si.StartupInfo.hStdInput) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdOutput; + if (si.StartupInfo.hStdError != INVALID_HANDLE_VALUE && + si.StartupInfo.hStdError != si.StartupInfo.hStdInput && + si.StartupInfo.hStdError != si.StartupInfo.hStdOutput) + stdhandles[stdhandles_count++] = si.StartupInfo.hStdError; + if (stdhandles_count) + si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; if (*argv && !strcmp(cmd, *argv)) wcmd[0] = L'\0'; @@ -1472,16 +1552,98 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen wenvblk = make_environment_block(deltaenv); memset(&pi, 0, sizeof(pi)); - ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, TRUE, - flags, wenvblk, dir ? wdir : NULL, &si, &pi); + if (restrict_handle_inheritance && stdhandles_count && + (InitializeProcThreadAttributeList(NULL, 1, 0, &size) || + GetLastError() == ERROR_INSUFFICIENT_BUFFER) && + (attr_list = (LPPROC_THREAD_ATTRIBUTE_LIST) + (HeapAlloc(GetProcessHeap(), 0, size))) && + InitializeProcThreadAttributeList(attr_list, 1, 0, &size) && + UpdateProcThreadAttribute(attr_list, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + stdhandles, + stdhandles_count * sizeof(HANDLE), + NULL, NULL)) { + si.lpAttributeList = attr_list; + flags |= EXTENDED_STARTUPINFO_PRESENT; + } + + ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, + stdhandles_count ? TRUE : FALSE, + flags, wenvblk, dir ? wdir : NULL, + &si.StartupInfo, &pi); + + /* + * On Windows 2008 R2, it seems that specifying certain types of handles + * (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an + * error. Rather than playing finicky and fragile games, let's just try + * to detect this situation and simply try again without restricting any + * handle inheritance. This is still better than failing to create + * processes. + */ + if (!ret && restrict_handle_inheritance && stdhandles_count) { + DWORD err = GetLastError(); + struct strbuf buf = STRBUF_INIT; + + if (err != ERROR_NO_SYSTEM_RESOURCES && + /* + * On Windows 7 and earlier, handles on pipes and character + * devices are inherited automatically, and cannot be + * specified in the thread handle list. Rather than trying + * to catch each and every corner case (and running the + * chance of *still* forgetting a few), let's just fall + * back to creating the process without trying to limit the + * handle inheritance. + */ + !(err == ERROR_INVALID_PARAMETER && + GetVersion() >> 16 < 9200) && + !getenv("SUPPRESS_HANDLE_INHERITANCE_WARNING")) { + DWORD fl = 0; + int i; + + setenv("SUPPRESS_HANDLE_INHERITANCE_WARNING", "1", 1); + + for (i = 0; i < stdhandles_count; i++) { + HANDLE h = stdhandles[i]; + strbuf_addf(&buf, "handle #%d: %p (type %lx, " + "handle info (%d) %lx\n", i, h, + GetFileType(h), + GetHandleInformation(h, &fl), + fl); + } + strbuf_addstr(&buf, "\nThis is a bug; please report it " + "at\nhttps://github.com/git-for-windows/" + "git/issues/new\n\n" + "To suppress this warning, please set " + "the environment variable\n\n" + "\tSUPPRESS_HANDLE_INHERITANCE_WARNING=1" + "\n"); + } + restrict_handle_inheritance = 0; + flags &= ~EXTENDED_STARTUPINFO_PRESENT; + ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL, + TRUE, flags, wenvblk, dir ? wdir : NULL, + &si.StartupInfo, &pi); + if (!ret) + errno = err_win_to_posix(GetLastError()); + if (ret && buf.len) { + warning("failed to restrict file handles (%ld)\n\n%s", + err, buf.buf); + } + strbuf_release(&buf); + } else if (!ret) + errno = err_win_to_posix(GetLastError()); + + if (si.lpAttributeList) + DeleteProcThreadAttributeList(si.lpAttributeList); + if (attr_list) + HeapFree(GetProcessHeap(), 0, attr_list); free(wenvblk); free(wargs); - if (!ret) { - errno = ENOENT; + if (!ret) return -1; - } + CloseHandle(pi.hThread); /* @@ -2367,6 +2529,50 @@ static void setup_windows_environment(void) } } +int is_valid_win32_path(const char *path) +{ + int preceding_space_or_period = 0, i = 0, periods = 0; + + if (!protect_ntfs) + return 1; + + skip_dos_drive_prefix((char **)&path); + + for (;;) { + char c = *(path++); + switch (c) { + case '\0': + case '/': case '\\': + /* cannot end in ` ` or `.`, except for `.` and `..` */ + if (preceding_space_or_period && + (i != periods || periods > 2)) + return 0; + if (!c) + return 1; + + i = periods = preceding_space_or_period = 0; + continue; + case '.': + periods++; + /* fallthru */ + case ' ': + preceding_space_or_period = 1; + i++; + continue; + case ':': /* DOS drive prefix was already skipped */ + case '<': case '>': case '"': case '|': case '?': case '*': + /* illegal character */ + return 0; + default: + if (c > '\0' && c < '\x20') + /* illegal character */ + return 0; + } + preceding_space_or_period = 0; + i++; + } +} + #if !defined(_MSC_VER) /* * Disable MSVCRT command line wildcard expansion (__getmainargs called from diff --git a/compat/mingw.h b/compat/mingw.h index 1a46334399..3ec9fc36a5 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -453,6 +453,20 @@ char *mingw_query_user_email(void); #endif /** + * Verifies that the given path is a valid one on Windows. + * + * In particular, path segments are disallowed which + * + * - end in a period or a space (except the special directories `.` and `..`). + * + * - contain any of the reserved characters, e.g. `:`, `;`, `*`, etc + * + * Returns 1 upon success, otherwise 0. + */ +int is_valid_win32_path(const char *path); +#define is_valid_path(path) is_valid_win32_path(path) + +/** * Converts UTF-8 encoded string to UTF-16LE. * * To support repositories with legacy-encoded file names, invalid UTF-8 bytes diff --git a/compat/win32/path-utils.c b/compat/win32/path-utils.c index d9d3641de8..ebf2f12eb6 100644 --- a/compat/win32/path-utils.c +++ b/compat/win32/path-utils.c @@ -1,5 +1,29 @@ #include "../../git-compat-util.h" +int win32_has_dos_drive_prefix(const char *path) +{ + int i; + + /* + * Does it start with an ASCII letter (i.e. highest bit not set), + * followed by a colon? + */ + if (!(0x80 & (unsigned char)*path)) + return *path && path[1] == ':' ? 2 : 0; + + /* + * While drive letters must be letters of the English alphabet, it is + * possible to assign virtually _any_ Unicode character via `subst` as + * a drive letter to "virtual drives". Even `1`, or `ä`. Or fun stuff + * like this: + * + * subst ֍: %USERPROFILE%\Desktop + */ + for (i = 1; i < 4 && (0x80 & (unsigned char)path[i]); i++) + ; /* skip first UTF-8 character */ + return path[i] == ':' ? i + 1 : 0; +} + int win32_skip_dos_drive_prefix(char **path) { int ret = has_dos_drive_prefix(*path); diff --git a/compat/win32/path-utils.h b/compat/win32/path-utils.h index 8ed062a6b7..f2e70872cd 100644 --- a/compat/win32/path-utils.h +++ b/compat/win32/path-utils.h @@ -1,8 +1,9 @@ #ifndef WIN32_PATH_UTILS_H #define WIN32_PATH_UTILS_H -#define has_dos_drive_prefix(path) \ - (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0) +int win32_has_dos_drive_prefix(const char *path); +#define has_dos_drive_prefix win32_has_dos_drive_prefix + int win32_skip_dos_drive_prefix(char **path); #define skip_dos_drive_prefix win32_skip_dos_drive_prefix static inline int win32_is_dir_sep(int c) diff --git a/compat/winansi.c b/compat/winansi.c index 54fd701cbf..c27b20a79d 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -662,10 +662,20 @@ void winansi_init(void) */ HANDLE winansi_get_osfhandle(int fd) { + HANDLE ret; + if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED)) return hconsole1; if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED)) return hconsole2; - return (HANDLE)_get_osfhandle(fd); + ret = (HANDLE)_get_osfhandle(fd); + + /* + * There are obviously circumstances under which _get_osfhandle() + * returns (HANDLE)-2. This is not documented anywhere, but that is so + * clearly an invalid handle value that we can just work around this + * and return the correct value for invalid handles. + */ + return ret == (HANDLE)-2 ? INVALID_HANDLE_VALUE : ret; } diff --git a/config.mak.uname b/config.mak.uname index cc8efd95b1..0ab8e00938 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -454,7 +454,6 @@ ifneq ($(USE_MSVC_CRTDBG),) # Optionally enable memory leak reporting. BASIC_CFLAGS += -DUSE_MSVC_CRTDBG endif - BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 # Always give "-Zi" to the compiler and "-debug" to linker (even in # release mode) to force a PDB to be generated (like RelWithDebInfo). BASIC_CFLAGS += -Zi @@ -616,7 +615,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) compat/win32/path-utils.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o - BASIC_CFLAGS += -DWIN32 -DPROTECT_NTFS_DEFAULT=1 + BASIC_CFLAGS += -DWIN32 EXTLIBS += -lws2_32 GITLIBS += git.res PTHREAD_LIBS = @@ -511,7 +511,7 @@ int url_is_local_not_ssh(const char *url) const char *colon = strchr(url, ':'); const char *slash = strchr(url, '/'); return !colon || (slash && slash < colon) || - has_dos_drive_prefix(url); + (has_dos_drive_prefix(url) && is_valid_path(url)); } static const char *prot_name(enum protocol protocol) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 67705da641..e4d9ff4a95 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1749,7 +1749,7 @@ __git_log_shortlog_options=" --all-match --invert-grep " -__git_log_pretty_formats="oneline short medium full fuller email raw format: mboxrd" +__git_log_pretty_formats="oneline short medium full fuller reference email raw format: tformat: mboxrd" __git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default raw unix format:" _git_log () @@ -2783,7 +2783,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init deinit update set-branch summary foreach sync absorbgitdirs" + local subcommands="add status init deinit update set-branch set-url summary foreach sync absorbgitdirs" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then case "$cur" in @@ -64,16 +64,16 @@ static time_t gm_time_t(timestamp_t time, int tz) * thing, which means that tz -0100 is passed in as the integer -100, * even though it means "sixty minutes off" */ -static struct tm *time_to_tm(timestamp_t time, int tz) +static struct tm *time_to_tm(timestamp_t time, int tz, struct tm *tm) { time_t t = gm_time_t(time, tz); - return gmtime(&t); + return gmtime_r(&t, tm); } -static struct tm *time_to_tm_local(timestamp_t time) +static struct tm *time_to_tm_local(timestamp_t time, struct tm *tm) { time_t t = time; - return localtime(&t); + return localtime_r(&t, tm); } /* @@ -283,6 +283,7 @@ static void show_date_normal(struct strbuf *buf, timestamp_t time, struct tm *tm const char *show_date(timestamp_t time, int tz, const struct date_mode *mode) { struct tm *tm; + struct tm tmbuf = { 0 }; struct tm human_tm = { 0 }; int human_tz = -1; static struct strbuf timebuf = STRBUF_INIT; @@ -318,11 +319,11 @@ const char *show_date(timestamp_t time, int tz, const struct date_mode *mode) } if (mode->local) - tm = time_to_tm_local(time); + tm = time_to_tm_local(time, &tmbuf); else - tm = time_to_tm(time, tz); + tm = time_to_tm(time, tz, &tmbuf); if (!tm) { - tm = time_to_tm(0, 0); + tm = time_to_tm(0, 0, &tmbuf); tz = 0; } @@ -959,10 +960,11 @@ void datestamp(struct strbuf *out) { time_t now; int offset; + struct tm tm = { 0 }; time(&now); - offset = tm_to_time_t(localtime(&now)) - now; + offset = tm_to_time_t(localtime_r(&now, &tm)) - now; offset /= 60; date_string(now, offset, out); @@ -2495,22 +2495,6 @@ static void pprint_rename(struct strbuf *name, const char *a, const char *b) } } -struct diffstat_t { - int nr; - int alloc; - struct diffstat_file { - char *from_name; - char *name; - char *print_name; - const char *comments; - unsigned is_unmerged:1; - unsigned is_binary:1; - unsigned is_renamed:1; - unsigned is_interesting:1; - uintmax_t added, deleted; - } **files; -}; - static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, const char *name_a, const char *name_b) @@ -3157,7 +3141,7 @@ static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o gather_dirstat(options, &dir, changed, "", 0); } -static void free_diffstat_info(struct diffstat_t *diffstat) +void free_diffstat_info(struct diffstat_t *diffstat) { int i; for (i = 0; i < diffstat->nr; i++) { @@ -6283,12 +6267,7 @@ void diff_flush(struct diff_options *options) dirstat_by_line) { struct diffstat_t diffstat; - memset(&diffstat, 0, sizeof(struct diffstat_t)); - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - diff_flush_stat(p, options, &diffstat); - } + compute_diffstat(options, &diffstat, q); if (output_format & DIFF_FORMAT_NUMSTAT) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) @@ -6621,6 +6600,20 @@ static int is_submodule_ignored(const char *path, struct diff_options *options) return ignored; } +void compute_diffstat(struct diff_options *options, + struct diffstat_t *diffstat, + struct diff_queue_struct *q) +{ + int i; + + memset(diffstat, 0, sizeof(struct diffstat_t)); + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (check_pair_status(p)) + diff_flush_stat(p, options, diffstat); + } +} + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const struct object_id *oid, @@ -245,6 +245,22 @@ void diff_emit_submodule_error(struct diff_options *o, const char *err); void diff_emit_submodule_pipethrough(struct diff_options *o, const char *line, int len); +struct diffstat_t { + int nr; + int alloc; + struct diffstat_file { + char *from_name; + char *name; + char *print_name; + const char *comments; + unsigned is_unmerged:1; + unsigned is_binary:1; + unsigned is_renamed:1; + unsigned is_interesting:1; + uintmax_t added, deleted; + } **files; +}; + enum color_diff { DIFF_RESET = 0, DIFF_CONTEXT = 1, @@ -334,6 +350,10 @@ void diff_change(struct diff_options *, struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat, + struct diff_queue_struct *q); +void free_diffstat_info(struct diffstat_t *diffstat); + #define DIFF_SETUP_REVERSE 1 #define DIFF_SETUP_USE_SIZE_CACHE 4 diff --git a/environment.c b/environment.c index efa072680a..6f0be4b7a2 100644 --- a/environment.c +++ b/environment.c @@ -78,7 +78,7 @@ enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; int protect_hfs = PROTECT_HFS_DEFAULT; #ifndef PROTECT_NTFS_DEFAULT -#define PROTECT_NTFS_DEFAULT 0 +#define PROTECT_NTFS_DEFAULT 1 #endif int protect_ntfs = PROTECT_NTFS_DEFAULT; const char *core_fsmonitor; diff --git a/fast-import.c b/fast-import.c index 9503d087b2..b8b65a801c 100644 --- a/fast-import.c +++ b/fast-import.c @@ -217,6 +217,7 @@ static uintmax_t next_mark; static struct strbuf new_data = STRBUF_INIT; static int seen_data_command; static int require_explicit_termination; +static int allow_unsafe_features; /* Signal handling */ static volatile sig_atomic_t checkpoint_requested; @@ -1682,6 +1683,12 @@ static void dump_marks(void) if (!export_marks_file || (import_marks_file && !import_marks_file_done)) return; + if (safe_create_leading_directories_const(export_marks_file)) { + failure |= error_errno("unable to create leading directories of %s", + export_marks_file); + return; + } + if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) { failure |= error_errno("Unable to write marks file %s", export_marks_file); @@ -3146,7 +3153,6 @@ static void option_import_marks(const char *marks, } import_marks_file = make_fast_import_path(marks); - safe_create_leading_directories_const(import_marks_file); import_marks_file_from_stream = from_stream; import_marks_file_ignore_missing = ignore_missing; } @@ -3187,7 +3193,6 @@ static void option_active_branches(const char *branches) static void option_export_marks(const char *marks) { export_marks_file = make_fast_import_path(marks); - safe_create_leading_directories_const(export_marks_file); } static void option_cat_blob_fd(const char *fd) @@ -3230,10 +3235,12 @@ static int parse_one_option(const char *option) option_active_branches(option); } else if (skip_prefix(option, "export-pack-edges=", &option)) { option_export_pack_edges(option); - } else if (starts_with(option, "quiet")) { + } else if (!strcmp(option, "quiet")) { show_stats = 0; - } else if (starts_with(option, "stats")) { + } else if (!strcmp(option, "stats")) { show_stats = 1; + } else if (!strcmp(option, "allow-unsafe-features")) { + ; /* already handled during early option parsing */ } else { return 0; } @@ -3241,6 +3248,13 @@ static int parse_one_option(const char *option) return 1; } +static void check_unsafe_feature(const char *feature, int from_stream) +{ + if (from_stream && !allow_unsafe_features) + die(_("feature '%s' forbidden in input without --allow-unsafe-features"), + feature); +} + static int parse_one_feature(const char *feature, int from_stream) { const char *arg; @@ -3248,10 +3262,13 @@ static int parse_one_feature(const char *feature, int from_stream) if (skip_prefix(feature, "date-format=", &arg)) { option_date_format(arg); } else if (skip_prefix(feature, "import-marks=", &arg)) { + check_unsafe_feature("import-marks", from_stream); option_import_marks(arg, from_stream, 0); } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) { + check_unsafe_feature("import-marks-if-exists", from_stream); option_import_marks(arg, from_stream, 1); } else if (skip_prefix(feature, "export-marks=", &arg)) { + check_unsafe_feature(feature, from_stream); option_export_marks(arg); } else if (!strcmp(feature, "alias")) { ; /* Don't die - this feature is supported */ @@ -3380,6 +3397,20 @@ 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)); + /* + * 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 + * line to override stream data). But we must do an early parse of any + * command-line options that impact how we interpret the feature lines. + */ + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-' || !strcmp(arg, "--")) + break; + if (!strcmp(arg, "--allow-unsafe-features")) + allow_unsafe_features = 1; + } + global_argc = argc; global_argv = argv; diff --git a/fetch-pack.c b/fetch-pack.c index ba92587023..1734a573b0 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -669,6 +669,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, save_commit_buffer = 0; + trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL); for (ref = *refs; ref; ref = ref->next) { struct object *o; @@ -680,7 +681,8 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, if (!o) continue; - /* We already have it -- which may mean that we were + /* + * We already have it -- which may mean that we were * in sync with the other side at some time after * that (it is OK if we guess wrong here). */ @@ -690,7 +692,13 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, cutoff = commit->date; } } + trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL); + /* + * This block marks all local refs as COMPLETE, and then recursively marks all + * parents of those refs as COMPLETE. + */ + trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL); if (!args->deepen) { for_each_ref(mark_complete_oid, NULL); for_each_cached_alternate(NULL, mark_alternate_complete); @@ -698,11 +706,13 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, if (cutoff) mark_recent_complete_commits(args, cutoff); } + trace2_region_leave("fetch-pack", "mark_complete_local_refs", NULL); /* * Mark all complete remote refs as common refs. * Don't mark them common yet; the server has to be told so first. */ + trace2_region_enter("fetch-pack", "mark_common_remote_refs", NULL); for (ref = *refs; ref; ref = ref->next) { struct object *o = deref_tag(the_repository, lookup_object(the_repository, @@ -715,6 +725,7 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, negotiator->known_common(negotiator, (struct commit *)o); } + trace2_region_leave("fetch-pack", "mark_common_remote_refs", NULL); save_commit_buffer = old_save_commit_buffer; } @@ -64,6 +64,7 @@ static struct oidset gitmodules_done = OIDSET_INIT; FUNC(GITMODULES_SYMLINK, ERROR) \ FUNC(GITMODULES_URL, ERROR) \ FUNC(GITMODULES_PATH, ERROR) \ + FUNC(GITMODULES_UPDATE, ERROR) \ /* warnings */ \ FUNC(BAD_FILEMODE, WARN) \ FUNC(EMPTY_NAME, WARN) \ @@ -595,7 +596,7 @@ static int fsck_tree(const struct object_id *oid, while (desc.size) { unsigned short mode; - const char *name; + const char *name, *backslash; const struct object_id *oid; oid = tree_entry_extract(&desc, &name, &mode); @@ -618,6 +619,22 @@ static int fsck_tree(const struct object_id *oid, ".gitmodules is a symbolic link"); } + if ((backslash = strchr(name, '\\'))) { + while (backslash) { + backslash++; + has_dotgit |= is_ntfs_dotgit(backslash); + if (is_ntfs_dotgitmodules(backslash)) { + if (!S_ISLNK(mode)) + oidset_insert(&gitmodules_found, oid); + else + retval += report(options, oid, OBJ_TREE, + FSCK_MSG_GITMODULES_SYMLINK, + ".gitmodules is a symbolic link"); + } + backslash = strchr(backslash, '\\'); + } + } + if (update_tree_entry_gently(&desc)) { retval += report(options, oid, OBJ_TREE, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree"); break; @@ -931,6 +948,12 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata) FSCK_MSG_GITMODULES_PATH, "disallowed submodule path: %s", value); + if (!strcmp(key, "update") && value && + parse_submodule_update_type(value) == SM_UPDATE_COMMAND) + data->ret |= report(data->options, data->oid, OBJ_BLOB, + FSCK_MSG_GITMODULES_UPDATE, + "disallowed submodule update setting: %s", + value); free(name); return 0; diff --git a/fsmonitor.c b/fsmonitor.c index 0477500b39..868cca01e2 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -191,13 +191,26 @@ void refresh_fsmonitor(struct index_state *istate) } if (bol < query_result.len) fsmonitor_refresh_callback(istate, buf + bol); + + /* Now mark the untracked cache for fsmonitor usage */ + if (istate->untracked) + istate->untracked->use_fsmonitor = 1; } else { + + /* We only want to run the post index changed hook if we've actually changed entries, so keep track + * if we actually changed entries or not */ + int is_cache_changed = 0; /* Mark all entries invalid */ - for (i = 0; i < istate->cache_nr; i++) - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; + for (i = 0; i < istate->cache_nr; i++) { + if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) { + is_cache_changed = 1; + istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; + } + } /* If we're going to check every file, ensure we save the results */ - istate->cache_changed |= FSMONITOR_CHANGED; + if (is_cache_changed) + istate->cache_changed |= FSMONITOR_CHANGED; if (istate->untracked) istate->untracked->use_fsmonitor = 0; @@ -259,9 +272,7 @@ void tweak_fsmonitor(struct index_state *istate) (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr); ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate); - /* Now mark the untracked cache for fsmonitor usage */ - if (istate->untracked) - istate->untracked->use_fsmonitor = 1; + refresh_fsmonitor(istate); } ewah_free(istate->fsmonitor_dirty); diff --git a/git-compat-util.h b/git-compat-util.h index 607dca7534..aed0b5d4f9 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -320,26 +320,6 @@ char *gitdirname(char *); #define PATH_MAX 4096 #endif -#ifndef PRIuMAX -#define PRIuMAX "llu" -#endif - -#ifndef SCNuMAX -#define SCNuMAX PRIuMAX -#endif - -#ifndef PRIu32 -#define PRIu32 "u" -#endif - -#ifndef PRIx32 -#define PRIx32 "x" -#endif - -#ifndef PRIo32 -#define PRIo32 "o" -#endif - typedef uintmax_t timestamp_t; #define PRItime PRIuMAX #define parse_timestamp strtoumax @@ -397,6 +377,10 @@ static inline int git_offset_1st_component(const char *path) #define offset_1st_component git_offset_1st_component #endif +#ifndef is_valid_path +#define is_valid_path(path) 1 +#endif + #ifndef find_last_dir_sep static inline char *git_find_last_dir_sep(const char *path) { diff --git a/git-submodule.sh b/git-submodule.sh index c7f58c5756..aaa1809d24 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -12,6 +12,7 @@ USAGE="[--quiet] [--cached] or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...) or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...] or: $dashless [--quiet] set-branch (--default|--branch <branch>) [--] <path> + or: $dashless [--quiet] set-url [--] <path> <newurl> or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> or: $dashless [--quiet] sync [--recursive] [--] [<path>...] @@ -36,6 +37,7 @@ reference= cached= recursive= init= +require_init= files= remote= nofetch= @@ -466,6 +468,10 @@ cmd_update() -i|--init) init=1 ;; + --require-init) + init=1 + require_init=1 + ;; --remote) remote=1 ;; @@ -548,6 +554,7 @@ cmd_update() ${reference:+"$reference"} \ ${dissociate:+"--dissociate"} \ ${depth:+--depth "$depth"} \ + ${require_init:+--require-init} \ $recommend_shallow \ $jobs \ -- \ @@ -761,6 +768,55 @@ cmd_set_branch() { } # +# Configures a submodule's remote url +# +# $@ = requested path, requested url +# +cmd_set_url() { + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + if test $# -ne 2 + then + usage + fi + + # we can't use `git submodule--helper name` here because internally, it + # hashes the path so a trailing slash could lead to an unintentional no match + name="$(git submodule--helper list "$1" | cut -f2)" + if test -z "$name" + then + exit 1 + fi + + url="$2" + if test -z "$url" + then + exit 1 + fi + + git submodule--helper config submodule."$name".url "$url" + git submodule--helper sync ${GIT_QUIET:+--quiet} "$name" +} + +# # Show commit summary for submodules in index or working tree # # If '--cached' is given, show summary between index and given commit, @@ -1059,7 +1115,7 @@ cmd_absorbgitdirs() while test $# != 0 && test -z "$command" do case "$1" in - add | foreach | init | deinit | update | set-branch | status | summary | sync | absorbgitdirs) + add | foreach | init | deinit | update | set-branch | set-url | status | summary | sync | absorbgitdirs) command=$1 ;; -q|--quiet) diff --git a/git-svn.perl b/git-svn.perl index 050f2a36f4..4aa208ff5f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -110,7 +110,7 @@ my ($_stdin, $_help, $_edit, $_template, $_shared, $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_before, $_after, - $_merge, $_strategy, $_preserve_merges, $_dry_run, $_parents, $_local, + $_merge, $_strategy, $_rebase_merges, $_dry_run, $_parents, $_local, $_prefix, $_no_checkout, $_url, $_verbose, $_commit_url, $_tag, $_merge_info, $_interactive, $_set_svn_props); @@ -270,7 +270,8 @@ my %cmd = ( 'local|l' => \$_local, 'fetch-all|all' => \$_fetch_all, 'dry-run|n' => \$_dry_run, - 'preserve-merges|p' => \$_preserve_merges, + 'rebase-merges|p' => \$_rebase_merges, + 'preserve-merges|p' => \$_rebase_merges, %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, 'Commit a diff between two trees', @@ -1054,7 +1055,7 @@ sub cmd_dcommit { 'If you are attempting to commit ', "merges, try running:\n\t", 'git rebase --interactive', - '--preserve-merges ', + '--rebase-merges ', $gs->refname, "\nBefore dcommitting"; } @@ -1717,7 +1718,7 @@ sub rebase_cmd { push @cmd, '-v' if $_verbose; push @cmd, qw/--merge/ if $_merge; push @cmd, "--strategy=$_strategy" if $_strategy; - push @cmd, "--preserve-merges" if $_preserve_merges; + push @cmd, "--rebase-merges" if $_rebase_merges; @cmd; } diff --git a/gpg-interface.c b/gpg-interface.c index d60115ca40..5134ce2780 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -105,6 +105,16 @@ static struct { { 0, "VALIDSIG ", GPG_STATUS_FINGERPRINT }, }; +static void replace_cstring(char **field, const char *line, const char *next) +{ + free(*field); + + if (line && next) + *field = xmemdupz(line, next - line); + else + *field = NULL; +} + static void parse_gpg_output(struct signature_check *sigc) { const char *buf = sigc->gpg_status; @@ -136,33 +146,43 @@ static void parse_gpg_output(struct signature_check *sigc) /* Do we have key information? */ if (sigcheck_gpg_status[i].flags & GPG_STATUS_KEYID) { next = strchrnul(line, ' '); - free(sigc->key); - sigc->key = xmemdupz(line, next - line); + replace_cstring(&sigc->key, line, next); /* Do we have signer information? */ if (*next && (sigcheck_gpg_status[i].flags & GPG_STATUS_UID)) { line = next + 1; next = strchrnul(line, '\n'); - free(sigc->signer); - sigc->signer = xmemdupz(line, next - line); + replace_cstring(&sigc->signer, line, next); } } /* Do we have fingerprint? */ if (sigcheck_gpg_status[i].flags & GPG_STATUS_FINGERPRINT) { - next = strchrnul(line, ' '); - free(sigc->fingerprint); - sigc->fingerprint = xmemdupz(line, next - line); + const char *limit; + char **field; - /* Skip interim fields */ + next = strchrnul(line, ' '); + replace_cstring(&sigc->fingerprint, line, next); + + /* + * Skip interim fields. The search is + * limited to the same line since only + * OpenPGP signatures has a field with + * the primary fingerprint. + */ + limit = strchrnul(line, '\n'); for (j = 9; j > 0; j--) { - if (!*next) + if (!*next || limit <= next) break; line = next + 1; next = strchrnul(line, ' '); } - next = strchrnul(line, '\n'); - free(sigc->primary_key_fingerprint); - sigc->primary_key_fingerprint = xmemdupz(line, next - line); + field = &sigc->primary_key_fingerprint; + if (!j) { + next = strchrnul(line, '\n'); + replace_cstring(field, line, next); + } else { + replace_cstring(field, NULL, NULL); + } } break; @@ -187,6 +207,55 @@ found_duplicate_status: FREE_AND_NULL(sigc->key); } +static int verify_signed_buffer(const char *payload, size_t payload_size, + const char *signature, size_t signature_size, + struct strbuf *gpg_output, + struct strbuf *gpg_status) +{ + struct child_process gpg = CHILD_PROCESS_INIT; + struct gpg_format *fmt; + struct tempfile *temp; + int ret; + struct strbuf buf = STRBUF_INIT; + + temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); + if (!temp) + return error_errno(_("could not create temporary file")); + if (write_in_full(temp->fd, signature, signature_size) < 0 || + close_tempfile_gently(temp) < 0) { + error_errno(_("failed writing detached signature to '%s'"), + temp->filename.buf); + delete_tempfile(&temp); + return -1; + } + + fmt = get_format_by_sig(signature); + if (!fmt) + BUG("bad signature '%s'", signature); + + argv_array_push(&gpg.args, fmt->program); + argv_array_pushv(&gpg.args, fmt->verify_args); + argv_array_pushl(&gpg.args, + "--status-fd=1", + "--verify", temp->filename.buf, "-", + NULL); + + if (!gpg_status) + gpg_status = &buf; + + sigchain_push(SIGPIPE, SIG_IGN); + ret = pipe_command(&gpg, payload, payload_size, + gpg_status, 0, gpg_output, 0); + sigchain_pop(SIGPIPE); + + delete_tempfile(&temp); + + ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); + strbuf_release(&buf); /* no matter it was used or not */ + + return ret; +} + int check_signature(const char *payload, size_t plen, const char *signature, size_t slen, struct signature_check *sigc) { @@ -331,51 +400,3 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig return 0; } - -int verify_signed_buffer(const char *payload, size_t payload_size, - const char *signature, size_t signature_size, - struct strbuf *gpg_output, struct strbuf *gpg_status) -{ - struct child_process gpg = CHILD_PROCESS_INIT; - struct gpg_format *fmt; - struct tempfile *temp; - int ret; - struct strbuf buf = STRBUF_INIT; - - temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); - if (!temp) - return error_errno(_("could not create temporary file")); - if (write_in_full(temp->fd, signature, signature_size) < 0 || - close_tempfile_gently(temp) < 0) { - error_errno(_("failed writing detached signature to '%s'"), - temp->filename.buf); - delete_tempfile(&temp); - return -1; - } - - fmt = get_format_by_sig(signature); - if (!fmt) - BUG("bad signature '%s'", signature); - - argv_array_push(&gpg.args, fmt->program); - argv_array_pushv(&gpg.args, fmt->verify_args); - argv_array_pushl(&gpg.args, - "--status-fd=1", - "--verify", temp->filename.buf, "-", - NULL); - - if (!gpg_status) - gpg_status = &buf; - - sigchain_push(SIGPIPE, SIG_IGN); - ret = pipe_command(&gpg, payload, payload_size, - gpg_status, 0, gpg_output, 0); - sigchain_pop(SIGPIPE); - - delete_tempfile(&temp); - - ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); - strbuf_release(&buf); /* no matter it was used or not */ - - return ret; -} diff --git a/gpg-interface.h b/gpg-interface.h index 3e624ec289..93cc3aff5c 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -46,15 +46,6 @@ size_t parse_signature(const char *buf, size_t size); int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); -/* - * Run "gpg" to see if the payload matches the detached signature. - * gpg_output, when set, receives the diagnostic output from GPG. - * gpg_status, when set, receives the status output from GPG. - */ -int verify_signed_buffer(const char *payload, size_t payload_size, - const char *signature, size_t signature_size, - struct strbuf *gpg_output, struct strbuf *gpg_status); - int git_gpg_config(const char *, const char *, void *); void set_signing_key(const char *); const char *get_signing_key(void); @@ -218,7 +218,7 @@ struct git_graph { int merge_layout; /* * The number of columns added to the graph by the current commit. For - * 2-way and octopus merges, this is is usually one less than the + * 2-way and octopus merges, this is usually one less than the * number of parents: * * | | | | | \ @@ -26,7 +26,7 @@ static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data) static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data) { - return free(pointer); + free(pointer); } #endif @@ -1,3 +1,6 @@ +#ifndef KWSET_H +#define KWSET_H + /* This file has been copied from commit e7ac713d^ in the GNU grep git * repository. A few small changes have been made to adapt the code to * Git. @@ -59,3 +62,4 @@ size_t kwsexec(kwset_t, char const *, size_t, struct kwsmatch *); /* Deallocate the given keyword set and all its associated storage. */ void kwsfree(kwset_t); +#endif /* KWSET_H */ diff --git a/log-tree.c b/log-tree.c index 923a299e70..4e32638de8 100644 --- a/log-tree.c +++ b/log-tree.c @@ -449,22 +449,22 @@ static void show_signature(struct rev_info *opt, struct commit *commit) { struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; - struct strbuf gpg_output = STRBUF_INIT; + struct signature_check sigc = { 0 }; int status; if (parse_signed_commit(commit, &payload, &signature) <= 0) goto out; - status = verify_signed_buffer(payload.buf, payload.len, - signature.buf, signature.len, - &gpg_output, NULL); - if (status && !gpg_output.len) - strbuf_addstr(&gpg_output, "No signature\n"); - - show_sig_lines(opt, status, gpg_output.buf); + status = check_signature(payload.buf, payload.len, signature.buf, + signature.len, &sigc); + if (status && sigc.result == 'N') + show_sig_lines(opt, status, "No signature\n"); + else { + show_sig_lines(opt, status, sigc.gpg_output); + signature_check_clear(&sigc); + } out: - strbuf_release(&gpg_output); strbuf_release(&payload); strbuf_release(&signature); } @@ -497,6 +497,7 @@ static int show_one_mergetag(struct commit *commit, struct object_id oid; struct tag *tag; struct strbuf verify_message; + struct signature_check sigc = { 0 }; int status, nth; size_t payload_size, gpg_message_offset; @@ -525,12 +526,13 @@ static int show_one_mergetag(struct commit *commit, status = -1; if (extra->len > payload_size) { /* could have a good signature */ - if (!verify_signed_buffer(extra->value, payload_size, - extra->value + payload_size, - extra->len - payload_size, - &verify_message, NULL)) + if (!check_signature(extra->value, payload_size, + extra->value + payload_size, + extra->len - payload_size, &sigc)) { + strbuf_addstr(&verify_message, sigc.gpg_output); + signature_check_clear(&sigc); status = 0; /* good */ - else if (verify_message.len <= gpg_message_offset) + } else if (verify_message.len <= gpg_message_offset) strbuf_addstr(&verify_message, "No signature\n"); /* otherwise we couldn't verify, which is shown as bad */ } @@ -770,7 +772,7 @@ void show_log(struct rev_info *opt) opts.use_color = opt->diffopt.use_color; diff_setup_done(&opts); show_range_diff(opt->rdiff1, opt->rdiff2, - opt->creation_factor, 1, &opts); + opt->creation_factor, 1, &opts, NULL); memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff)); } diff --git a/object-store.h b/object-store.h index 7f7b3cdd80..55ee639350 100644 --- a/object-store.h +++ b/object-store.h @@ -60,6 +60,7 @@ struct oid_array *odb_loose_cache(struct object_directory *odb, void odb_clear_loose_cache(struct object_directory *odb); struct packed_git { + struct hashmap_entry packmap_ent; struct packed_git *next; struct list_head mru; struct pack_window *windows; @@ -88,6 +89,20 @@ struct packed_git { struct multi_pack_index; +static inline int pack_map_entry_cmp(const void *unused_cmp_data, + const struct hashmap_entry *entry, + const struct hashmap_entry *entry2, + const void *keydata) +{ + const char *key = keydata; + const struct packed_git *pg1, *pg2; + + pg1 = container_of(entry, const struct packed_git, packmap_ent); + pg2 = container_of(entry2, const struct packed_git, packmap_ent); + + return strcmp(pg1->pack_name, key ? key : pg2->pack_name); +} + struct raw_object_store { /* * Set of all object directories; the main directory is first (and @@ -132,6 +147,12 @@ struct raw_object_store { struct list_head packed_git_mru; /* + * A map of packfiles to packed_git structs for tracking which + * packs have been loaded already. + */ + struct hashmap pack_map; + + /* * A fast, rough count of the number of objects in the repository. * These two fields are not meant for direct access. Use * approximate_object_count() instead. @@ -479,6 +479,7 @@ struct raw_object_store *raw_object_store_new(void) memset(o, 0, sizeof(*o)); INIT_LIST_HEAD(&o->packed_git_mru); + hashmap_init(&o->pack_map, pack_map_entry_cmp, NULL, 0); return o; } @@ -518,6 +519,8 @@ void raw_object_store_clear(struct raw_object_store *o) INIT_LIST_HEAD(&o->packed_git_mru); close_object_store(o); o->packed_git = NULL; + + hashmap_free(&o->pack_map); } void parsed_object_pool_clear(struct parsed_object_pool *o) diff --git a/packfile.c b/packfile.c index 355066de17..f0dc63e92f 100644 --- a/packfile.c +++ b/packfile.c @@ -757,6 +757,9 @@ void install_packed_git(struct repository *r, struct packed_git *pack) pack->next = r->objects->packed_git; r->objects->packed_git = pack; + + hashmap_entry_init(&pack->packmap_ent, strhash(pack->pack_name)); + hashmap_add(&r->objects->pack_map, &pack->packmap_ent); } void (*report_garbage)(unsigned seen_bits, const char *path); @@ -856,20 +859,18 @@ static void prepare_pack(const char *full_name, size_t full_name_len, if (strip_suffix_mem(full_name, &base_len, ".idx") && !(data->m && midx_contains_pack(data->m, file_name))) { - /* Don't reopen a pack we already have. */ - for (p = data->r->objects->packed_git; p; p = p->next) { - size_t len; - if (strip_suffix(p->pack_name, ".pack", &len) && - len == base_len && - !memcmp(p->pack_name, full_name, len)) - break; - } + struct hashmap_entry hent; + char *pack_name = xstrfmt("%.*s.pack", (int)base_len, full_name); + unsigned int hash = strhash(pack_name); + hashmap_entry_init(&hent, hash); - if (!p) { + /* Don't reopen a pack we already have. */ + if (!hashmap_get(&data->r->objects->pack_map, &hent, pack_name)) { p = add_packed_git(full_name, full_name_len, data->local); if (p) install_packed_git(data->r, p); } + free(pack_name); } if (!report_garbage) diff --git a/parse-options.h b/parse-options.h index 38a33a087e..c6cc01e715 100644 --- a/parse-options.h +++ b/parse-options.h @@ -330,5 +330,7 @@ int parse_opt_passthru_argv(const struct option *, const char *, int); #define OPT_WITH(v, h) _OPT_CONTAINS_OR_WITH("with", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG) #define OPT_WITHOUT(v, h) _OPT_CONTAINS_OR_WITH("without", v, h, PARSE_OPT_HIDDEN | PARSE_OPT_NONEG) #define OPT_CLEANUP(v) OPT_STRING(0, "cleanup", v, N_("mode"), N_("how to strip spaces and #comments from message")) +#define OPT_PATHSPEC_FROM_FILE(v) OPT_FILENAME(0, "pathspec-from-file", v, N_("read pathspec from file")) +#define OPT_PATHSPEC_FILE_NUL(v) OPT_BOOL(0, "pathspec-file-nul", v, N_("with --pathspec-from-file, pathspec elements are separated with NUL character")) #endif @@ -1334,37 +1334,77 @@ int daemon_avoid_alias(const char *p) } } -static int only_spaces_and_periods(const char *path, size_t len, size_t skip) +/* + * On NTFS, we need to be careful to disallow certain synonyms of the `.git/` + * directory: + * + * - For historical reasons, file names that end in spaces or periods are + * automatically trimmed. Therefore, `.git . . ./` is a valid way to refer + * to `.git/`. + * + * - For other historical reasons, file names that do not conform to the 8.3 + * format (up to eight characters for the basename, three for the file + * extension, certain characters not allowed such as `+`, etc) are associated + * with a so-called "short name", at least on the `C:` drive by default. + * Which means that `git~1/` is a valid way to refer to `.git/`. + * + * Note: Technically, `.git/` could receive the short name `git~2` if the + * short name `git~1` were already used. In Git, however, we guarantee that + * `.git` is the first item in a directory, therefore it will be associated + * with the short name `git~1` (unless short names are disabled). + * + * - For yet other historical reasons, NTFS supports so-called "Alternate Data + * Streams", i.e. metadata associated with a given file, referred to via + * `<filename>:<stream-name>:<stream-type>`. There exists a default stream + * type for directories, allowing `.git/` to be accessed via + * `.git::$INDEX_ALLOCATION/`. + * + * When this function returns 1, it indicates that the specified file/directory + * name refers to a `.git` file or directory, or to any of these synonyms, and + * Git should therefore not track it. + * + * For performance reasons, _all_ Alternate Data Streams of `.git/` are + * forbidden, not just `::$INDEX_ALLOCATION`. + * + * This function is intended to be used by `git fsck` even on platforms where + * the backslash is a regular filename character, therefore it needs to handle + * backlash characters in the provided `name` specially: they are interpreted + * as directory separators. + */ +int is_ntfs_dotgit(const char *name) { - if (len < skip) + char c; + + /* + * Note that when we don't find `.git` or `git~1` we end up with `name` + * advanced partway through the string. That's okay, though, as we + * return immediately in those cases, without looking at `name` any + * further. + */ + c = *(name++); + if (c == '.') { + /* .git */ + if (((c = *(name++)) != 'g' && c != 'G') || + ((c = *(name++)) != 'i' && c != 'I') || + ((c = *(name++)) != 't' && c != 'T')) + return 0; + } else if (c == 'g' || c == 'G') { + /* git ~1 */ + if (((c = *(name++)) != 'i' && c != 'I') || + ((c = *(name++)) != 't' && c != 'T') || + *(name++) != '~' || + *(name++) != '1') + return 0; + } else return 0; - len -= skip; - path += skip; - while (len-- > 0) { - char c = *(path++); - if (c != ' ' && c != '.') + + for (;;) { + c = *(name++); + if (!c || c == '\\' || c == '/' || c == ':') + return 1; + if (c != '.' && c != ' ') return 0; } - return 1; -} - -int is_ntfs_dotgit(const char *name) -{ - size_t len; - - for (len = 0; ; len++) - if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { - if (only_spaces_and_periods(name, len, 4) && - !strncasecmp(name, ".git", 4)) - return 1; - if (only_spaces_and_periods(name, len, 5) && - !strncasecmp(name, "git~1", 5)) - return 1; - if (name[len] != '\\') - return 0; - name += len + 1; - len = -1; - } } static int is_ntfs_dot_generic(const char *name, @@ -1380,7 +1420,7 @@ static int is_ntfs_dot_generic(const char *name, only_spaces_and_periods: for (;;) { char c = name[i++]; - if (!c) + if (!c || c == ':') return 1; if (c != ' ' && c != '.') return 0; diff --git a/pathspec.c b/pathspec.c index 12c2b322b3..128f27fcb7 100644 --- a/pathspec.c +++ b/pathspec.c @@ -3,6 +3,8 @@ #include "dir.h" #include "pathspec.h" #include "attr.h" +#include "argv-array.h" +#include "quote.h" /* * Finds which of the given pathspecs match items in the index. @@ -613,6 +615,42 @@ void parse_pathspec(struct pathspec *pathspec, } } +void parse_pathspec_file(struct pathspec *pathspec, unsigned magic_mask, + unsigned flags, const char *prefix, + const char *file, int nul_term_line) +{ + struct argv_array parsed_file = ARGV_ARRAY_INIT; + strbuf_getline_fn getline_fn = nul_term_line ? strbuf_getline_nul : + strbuf_getline; + struct strbuf buf = STRBUF_INIT; + struct strbuf unquoted = STRBUF_INIT; + FILE *in; + + if (!strcmp(file, "-")) + in = stdin; + else + in = xfopen(file, "r"); + + while (getline_fn(&buf, in) != EOF) { + if (!nul_term_line && buf.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, buf.buf, NULL)) + die(_("line is badly quoted: %s"), buf.buf); + strbuf_swap(&buf, &unquoted); + } + argv_array_push(&parsed_file, buf.buf); + strbuf_reset(&buf); + } + + strbuf_release(&unquoted); + strbuf_release(&buf); + if (in != stdin) + fclose(in); + + parse_pathspec(pathspec, magic_mask, flags, prefix, parsed_file.argv); + argv_array_clear(&parsed_file); +} + void copy_pathspec(struct pathspec *dst, const struct pathspec *src) { int i, j; diff --git a/pathspec.h b/pathspec.h index 1c18a2c90c..a27dc81ba2 100644 --- a/pathspec.h +++ b/pathspec.h @@ -85,6 +85,16 @@ void parse_pathspec(struct pathspec *pathspec, unsigned flags, const char *prefix, const char **args); +/* + * Same as parse_pathspec() but uses file as input. + * When 'file' is exactly "-" it uses 'stdin' instead. + */ +void parse_pathspec_file(struct pathspec *pathspec, + unsigned magic_mask, + unsigned flags, + const char *prefix, + const char *file, + int nul_term_line); void copy_pathspec(struct pathspec *dst, const struct pathspec *src); void clear_pathspec(struct pathspec *); diff --git a/pkt-line.h b/pkt-line.h index 5c62015db4..fef3a0d792 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -77,7 +77,7 @@ int packet_read(int fd, char **src_buffer, size_t *src_len, char /* * Read a packetized line into a buffer like the 'packet_read()' function but * returns an 'enum packet_read_status' which indicates the status of the read. - * The number of bytes read will be assigined to *pktlen if the status of the + * The number of bytes read will be assigned to *pktlen if the status of the * read was 'PACKET_READ_NORMAL'. */ enum packet_read_status { @@ -20,6 +20,7 @@ static struct cmt_fmt_map { int is_tformat; int expand_tabs_in_log; int is_alias; + enum date_mode_type default_date_mode_type; const char *user_format; } *commit_formats; static size_t builtin_formats_len; @@ -97,7 +98,9 @@ static void setup_commit_formats(void) { "mboxrd", CMIT_FMT_MBOXRD, 0, 0 }, { "fuller", CMIT_FMT_FULLER, 0, 8 }, { "full", CMIT_FMT_FULL, 0, 8 }, - { "oneline", CMIT_FMT_ONELINE, 1, 0 } + { "oneline", CMIT_FMT_ONELINE, 1, 0 }, + { "reference", CMIT_FMT_USERFORMAT, 1, 0, + 0, DATE_SHORT, "%C(auto)%h (%s, %ad)" }, /* * Please update $__git_log_pretty_formats in * git-completion.bash when you add new formats. @@ -181,6 +184,8 @@ void get_commit_format(const char *arg, struct rev_info *rev) rev->commit_format = commit_format->format; rev->use_terminator = commit_format->is_tformat; rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log; + if (!rev->date_mode_explicit && commit_format->default_date_mode_type) + rev->date_mode.type = commit_format->default_date_mode_type; if (commit_format->format == CMIT_FMT_USERFORMAT) { save_user_format(rev, commit_format->user_format, commit_format->is_tformat); @@ -738,6 +743,9 @@ static size_t format_person_part(struct strbuf *sb, char part, case 'I': /* date, ISO 8601 strict */ strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT))); return placeholder_len; + case 's': + strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(SHORT))); + return placeholder_len; } skip: @@ -1617,14 +1625,14 @@ void repo_format_commit_message(struct repository *r, const char *format, struct strbuf *sb, const struct pretty_print_context *pretty_ctx) { - struct format_commit_context context; + struct format_commit_context context = { + .commit = commit, + .pretty_ctx = pretty_ctx, + .wrap_start = sb->len + }; const char *output_enc = pretty_ctx->output_encoding; const char *utf8 = "UTF-8"; - memset(&context, 0, sizeof(context)); - context.commit = commit; - context.pretty_ctx = pretty_ctx; - context.wrap_start = sb->len; /* * convert a commit message to UTF-8 first * as far as 'format_commit_item' assumes it in UTF-8 diff --git a/progress.c b/progress.c index 0063559aab..19805ac646 100644 --- a/progress.c +++ b/progress.c @@ -14,6 +14,7 @@ #include "strbuf.h" #include "trace.h" #include "utf8.h" +#include "config.h" #define TP_IDX_MAX 8 @@ -267,9 +268,19 @@ static struct progress *start_progress_delay(const char *title, uint64_t total, return progress; } +static int get_default_delay(void) +{ + static int delay_in_secs = -1; + + if (delay_in_secs < 0) + delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2); + + return delay_in_secs; +} + struct progress *start_delayed_progress(const char *title, uint64_t total) { - return start_progress_delay(title, total, 2, 0); + return start_progress_delay(title, total, get_default_delay(), 0); } struct progress *start_progress(const char *title, uint64_t total) @@ -294,7 +305,7 @@ struct progress *start_sparse_progress(const char *title, uint64_t total) struct progress *start_delayed_sparse_progress(const char *title, uint64_t total) { - return start_progress_delay(title, total, 2, 1); + return start_progress_delay(title, total, get_default_delay(), 1); } static void finish_if_sparse(struct progress *progress) diff --git a/range-diff.c b/range-diff.c index 7fed5a3b4b..f56b4012a2 100644 --- a/range-diff.c +++ b/range-diff.c @@ -40,7 +40,8 @@ static size_t find_end_of_line(char *buffer, unsigned long size) * Reads the patches into a string list, with the `util` field being populated * as struct object_id (will need to be free()d). */ -static int read_patches(const char *range, struct string_list *list) +static int read_patches(const char *range, struct string_list *list, + struct argv_array *other_arg) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT; @@ -61,8 +62,11 @@ static int read_patches(const char *range, struct string_list *list) "--output-indicator-new=>", "--output-indicator-old=<", "--output-indicator-context=#", - "--no-abbrev-commit", range, + "--no-abbrev-commit", NULL); + if (other_arg) + argv_array_pushv(&cp.args, other_arg->argv); + argv_array_push(&cp.args, range); cp.out = -1; cp.no_stdin = 1; cp.git_cmd = 1; @@ -144,6 +148,12 @@ static int read_patches(const char *range, struct string_list *list) strbuf_addstr(&buf, line); strbuf_addstr(&buf, "\n\n"); strbuf_addstr(&buf, " ## Commit message ##\n"); + } else if (starts_with(line, "Notes") && + line[strlen(line) - 1] == ':') { + strbuf_addstr(&buf, "\n\n"); + /* strip the trailing colon */ + strbuf_addf(&buf, " ## %.*s ##\n", + (int)(strlen(line) - 1), line); } else if (starts_with(line, " ")) { p = line + len - 2; while (isspace(*p) && p >= line) @@ -496,16 +506,17 @@ static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data) int show_range_diff(const char *range1, const char *range2, int creation_factor, int dual_color, - struct diff_options *diffopt) + struct diff_options *diffopt, + struct argv_array *other_arg) { int res = 0; struct string_list branch1 = STRING_LIST_INIT_DUP; struct string_list branch2 = STRING_LIST_INIT_DUP; - if (read_patches(range1, &branch1)) + if (read_patches(range1, &branch1, other_arg)) res = error(_("could not parse log for '%s'"), range1); - if (!res && read_patches(range2, &branch2)) + if (!res && read_patches(range2, &branch2, other_arg)) res = error(_("could not parse log for '%s'"), range2); if (!res) { diff --git a/range-diff.h b/range-diff.h index 77347a9f88..c57ec7dab5 100644 --- a/range-diff.h +++ b/range-diff.h @@ -2,6 +2,7 @@ #define RANGE_DIFF_H #include "diff.h" +#include "argv-array.h" #define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60 @@ -12,6 +13,7 @@ */ int show_range_diff(const char *range1, const char *range2, int creation_factor, int dual_color, - struct diff_options *diffopt); + struct diff_options *diffopt, + struct argv_array *other_arg); #endif diff --git a/read-cache.c b/read-cache.c index 06744287a1..ad0b48c84d 100644 --- a/read-cache.c +++ b/read-cache.c @@ -964,6 +964,9 @@ int verify_path(const char *path, unsigned mode) if (has_dos_drive_prefix(path)) return 0; + if (!is_valid_path(path)) + return 0; + goto inside; for (;;) { if (!c) @@ -991,7 +994,15 @@ inside: if ((c == '.' && !verify_dotfile(path, mode)) || is_dir_sep(c) || c == '\0') return 0; + } else if (c == '\\' && protect_ntfs) { + if (is_ntfs_dotgit(path)) + return 0; + if (S_ISLNK(mode)) { + if (is_ntfs_dotgitmodules(path)) + return 0; + } } + c = *path++; } } diff --git a/revision.c b/revision.c index 0e39b2b8a5..39a25e7a5d 100644 --- a/revision.c +++ b/revision.c @@ -3098,7 +3098,7 @@ static void set_children(struct rev_info *revs) void reset_revision_walk(void) { - clear_object_flags(SEEN | ADDED | SHOWN); + clear_object_flags(SEEN | ADDED | SHOWN | TOPO_WALK_EXPLORED | TOPO_WALK_INDEGREE); } static int mark_uninteresting(const struct object_id *oid, @@ -3211,10 +3211,26 @@ static void compute_indegrees_to_depth(struct rev_info *revs, indegree_walk_step(revs); } +static void reset_topo_walk(struct rev_info *revs) +{ + struct topo_walk_info *info = revs->topo_walk_info; + + clear_prio_queue(&info->explore_queue); + clear_prio_queue(&info->indegree_queue); + clear_prio_queue(&info->topo_queue); + clear_indegree_slab(&info->indegree); + clear_author_date_slab(&info->author_date); + + FREE_AND_NULL(revs->topo_walk_info); +} + static void init_topo_walk(struct rev_info *revs) { struct topo_walk_info *info; struct commit_list *list; + if (revs->topo_walk_info) + reset_topo_walk(revs); + revs->topo_walk_info = xmalloc(sizeof(struct topo_walk_info)); info = revs->topo_walk_info; memset(info, 0, sizeof(struct topo_walk_info)); @@ -3944,7 +3960,7 @@ struct commit *get_revision(struct rev_info *revs) return c; } -char *get_revision_mark(const struct rev_info *revs, const struct commit *commit) +const char *get_revision_mark(const struct rev_info *revs, const struct commit *commit) { if (commit->object.flags & BOUNDARY) return "-"; @@ -3966,7 +3982,7 @@ char *get_revision_mark(const struct rev_info *revs, const struct commit *commit void put_revision_mark(const struct rev_info *revs, const struct commit *commit) { - char *mark = get_revision_mark(revs, commit); + const char *mark = get_revision_mark(revs, commit); if (!strlen(mark)) return; fputs(mark, stdout); diff --git a/revision.h b/revision.h index 4134dc6029..addd69410b 100644 --- a/revision.h +++ b/revision.h @@ -322,8 +322,8 @@ int handle_revision_arg(const char *arg, struct rev_info *revs, void reset_revision_walk(void); int prepare_revision_walk(struct rev_info *revs); struct commit *get_revision(struct rev_info *revs); -char *get_revision_mark(const struct rev_info *revs, - const struct commit *commit); +const char *get_revision_mark(const struct rev_info *revs, + const struct commit *commit); void put_revision_mark(const struct rev_info *revs, const struct commit *commit); diff --git a/run-command.c b/run-command.c index 3449db319b..9942f120a9 100644 --- a/run-command.c +++ b/run-command.c @@ -412,8 +412,7 @@ static int prepare_cmd(struct argv_array *out, const struct child_process *cmd) argv_array_push(out, SHELL_PATH); if (cmd->git_cmd) { - argv_array_push(out, "git"); - argv_array_pushv(out, cmd->argv); + prepare_git_cmd(out, cmd->argv); } else if (cmd->use_shell) { prepare_shell_cmd(out, cmd->argv); } else { diff --git a/send-pack.c b/send-pack.c index a7322d3278..0407841ae8 100644 --- a/send-pack.c +++ b/send-pack.c @@ -41,7 +41,9 @@ int option_parse_push_signed(const struct option *opt, static void feed_object(const struct object_id *oid, FILE *fh, int negative) { if (negative && - !has_object_file_with_flags(oid, OBJECT_INFO_SKIP_FETCH_OBJECT)) + !has_object_file_with_flags(oid, + OBJECT_INFO_SKIP_FETCH_OBJECT | + OBJECT_INFO_QUICK)) return; if (negative) diff --git a/sequencer.c b/sequencer.c index 2c6fbcb41a..763ccbbc45 100644 --- a/sequencer.c +++ b/sequencer.c @@ -147,6 +147,8 @@ static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete") * command-line. */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") +static GIT_PATH_FUNC(rebase_path_cdate_is_adate, "rebase-merge/cdate_is_adate") +static GIT_PATH_FUNC(rebase_path_ignore_date, "rebase-merge/ignore_date") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") static GIT_PATH_FUNC(rebase_path_verbose, "rebase-merge/verbose") static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") @@ -823,9 +825,19 @@ int read_author_script(const char *path, char **name, char **email, char **date, error(_("missing 'GIT_AUTHOR_DATE'")); if (date_i < 0 || email_i < 0 || date_i < 0 || err) goto finish; - *name = kv.items[name_i].util; - *email = kv.items[email_i].util; - *date = kv.items[date_i].util; + + if (name) + *name = kv.items[name_i].util; + else + free(kv.items[name_i].util); + if (email) + *email = kv.items[email_i].util; + else + free(kv.items[email_i].util); + if (date) + *date = kv.items[date_i].util; + else + free(kv.items[date_i].util); retval = 0; finish: string_list_clear(&kv, !!retval); @@ -868,6 +880,47 @@ static char *get_author(const char *message) return NULL; } +/* Returns a "date" string that needs to be free()'d by the caller */ +static char *read_author_date_or_null(void) +{ + char *date; + + if (read_author_script(rebase_path_author_script(), + NULL, NULL, &date, 0)) + return NULL; + return date; +} + +/* Construct a free()able author string with current time as the author date */ +static char *ignore_author_date(const char *author) +{ + int len = strlen(author); + struct ident_split ident; + struct strbuf new_author = STRBUF_INIT; + + if (split_ident_line(&ident, author, len) < 0) { + error(_("malformed ident line")); + return NULL; + } + len = ident.mail_end - ident.name_begin + 1; + + strbuf_addf(&new_author, "%.*s ", len, ident.name_begin); + datestamp(&new_author); + return strbuf_detach(&new_author, NULL); +} + +static void push_dates(struct child_process *child, int change_committer_date) +{ + time_t now = time(NULL); + struct strbuf date = STRBUF_INIT; + + strbuf_addf(&date, "@%"PRIuMAX, (uintmax_t)now); + argv_array_pushf(&child->env_array, "GIT_AUTHOR_DATE=%s", date.buf); + if (change_committer_date) + argv_array_pushf(&child->env_array, "GIT_COMMITTER_DATE=%s", date.buf); + strbuf_release(&date); +} + static const char staged_changes_advice[] = N_("you have staged changes in your working tree\n" "If these changes are meant to be squashed into the previous commit, run:\n" @@ -927,6 +980,25 @@ static int run_git_commit(struct repository *r, cmd.git_cmd = 1; + if (opts->committer_date_is_author_date) { + int res = -1; + struct strbuf datebuf = STRBUF_INIT; + char *date = read_author_date_or_null(); + + if (!date) + return -1; + + strbuf_addf(&datebuf, "@%s", date); + res = setenv("GIT_COMMITTER_DATE", + opts->ignore_date ? "" : datebuf.buf, 1); + + strbuf_release(&datebuf); + free(date); + + if (res) + return -1; + } + if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) { const char *gpg_opt = gpg_sign_opt_quoted(opts); @@ -942,6 +1014,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); + if (opts->ignore_date) + push_dates(&cmd, opts->committer_date_is_author_date); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); else if (!(flags & EDIT_MSG)) @@ -1310,14 +1384,13 @@ static int try_to_commit(struct repository *r, struct commit_extra_header *extra = NULL; struct strbuf err = STRBUF_INIT; struct strbuf commit_msg = STRBUF_INIT; - char *amend_author = NULL; + char *author_to_free = NULL; const char *hook_commit = NULL; enum commit_msg_cleanup_mode cleanup; int res = 0; if (parse_head(r, ¤t_head)) return -1; - if (flags & AMEND_MSG) { const char *exclude_gpgsig[] = { "gpgsig", NULL }; const char *out_enc = get_commit_output_encoding(); @@ -1332,7 +1405,7 @@ static int try_to_commit(struct repository *r, strbuf_addstr(msg, orig_message); hook_commit = "HEAD"; } - author = amend_author = get_author(message); + author = author_to_free = get_author(message); unuse_commit_buffer(current_head, message); if (!author) { res = error(_("unable to parse commit author")); @@ -1345,16 +1418,57 @@ static int try_to_commit(struct repository *r, commit_list_insert(current_head, &parents); } + if (opts->committer_date_is_author_date) { + int len = strlen(author); + struct ident_split ident; + struct strbuf date = STRBUF_INIT; + + if (split_ident_line(&ident, author, len) < 0) { + res = error(_("malformed ident line")); + goto out; + } + if (!ident.date_begin) { + res = error(_("corrupted author without date information")); + goto out; + } + + strbuf_addf(&date, "@%.*s %.*s", + (int)(ident.date_end - ident.date_begin), ident.date_begin, + (int)(ident.tz_end - ident.tz_begin), ident.tz_begin); + res = setenv("GIT_COMMITTER_DATE", + opts->ignore_date ? "" : date.buf, 1); + strbuf_release(&date); + + if (res) + goto out; + } + if (write_index_as_tree(&tree, r->index, r->index_file, 0, NULL)) { res = error(_("git write-tree failed to write a tree")); goto out; } - if (!(flags & ALLOW_EMPTY) && oideq(current_head ? - get_commit_tree_oid(current_head) : - the_hash_algo->empty_tree, &tree)) { - res = 1; /* run 'git commit' to display error message */ - goto out; + if (!(flags & ALLOW_EMPTY)) { + struct commit *first_parent = current_head; + + if (flags & AMEND_MSG) { + if (current_head->parents) { + first_parent = current_head->parents->item; + if (repo_parse_commit(r, first_parent)) { + res = error(_("could not parse HEAD commit")); + goto out; + } + } else { + first_parent = NULL; + } + } + if (oideq(first_parent + ? get_commit_tree_oid(first_parent) + : the_hash_algo->empty_tree, + &tree)) { + res = 1; /* run 'git commit' to display error message */ + goto out; + } } if (find_hook("prepare-commit-msg")) { @@ -1388,6 +1502,15 @@ static int try_to_commit(struct repository *r, reset_ident_date(); + if (opts->ignore_date) { + author = ignore_author_date(author); + if (!author) { + res = -1; + goto out; + } + free(author_to_free); + author_to_free = (char *)author; + } if (commit_tree_extended(msg->buf, msg->len, &tree, parents, oid, author, opts->gpg_sign, extra)) { res = error(_("failed to write commit object")); @@ -1408,7 +1531,7 @@ out: free_commit_extra_headers(extra); strbuf_release(&err); strbuf_release(&commit_msg); - free(amend_author); + free(author_to_free); return res; } @@ -2007,6 +2130,7 @@ void todo_list_release(struct todo_list *todo_list) static struct todo_item *append_new_todo(struct todo_list *todo_list) { ALLOC_GROW(todo_list->items, todo_list->nr + 1, todo_list->alloc); + todo_list->total_nr++; return todo_list->items + todo_list->nr++; } @@ -2278,6 +2402,16 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose) sequencer_remove_state(&opts); } +static void todo_list_write_total_nr(struct todo_list *todo_list) +{ + FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); + + if (f) { + fprintf(f, "%d\n", todo_list->total_nr); + fclose(f); + } +} + static int read_populate_todo(struct repository *r, struct todo_list *todo_list, struct replay_opts *opts) @@ -2323,7 +2457,6 @@ static int read_populate_todo(struct repository *r, if (is_rebase_i(opts)) { struct todo_list done = TODO_LIST_INIT; - FILE *f = fopen_or_warn(rebase_path_msgtotal(), "w"); if (strbuf_read_file(&done.buf, rebase_path_done(), 0) > 0 && !todo_list_parse_insn_buffer(r, done.buf.buf, &done)) @@ -2335,10 +2468,7 @@ static int read_populate_todo(struct repository *r, + count_commands(todo_list); todo_list_release(&done); - if (f) { - fprintf(f, "%d\n", todo_list->total_nr); - fclose(f); - } + todo_list_write_total_nr(todo_list); } return 0; @@ -2469,6 +2599,16 @@ static int read_populate_opts(struct replay_opts *opts) opts->signoff = 1; } + if (file_exists(rebase_path_cdate_is_adate())) { + opts->allow_ff = 0; + opts->committer_date_is_author_date = 1; + } + + if (file_exists(rebase_path_ignore_date())) { + opts->allow_ff = 0; + opts->ignore_date = 1; + } + if (file_exists(rebase_path_reschedule_failed_exec())) opts->reschedule_failed_exec = 1; @@ -2551,6 +2691,10 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign); if (opts->signoff) write_file(rebase_path_signoff(), "--signoff\n"); + if (opts->committer_date_is_author_date) + write_file(rebase_path_cdate_is_adate(), "%s", ""); + if (opts->ignore_date) + write_file(rebase_path_ignore_date(), "%s", ""); if (opts->reschedule_failed_exec) write_file(rebase_path_reschedule_failed_exec(), "%s", ""); @@ -3491,6 +3635,8 @@ static int do_merge(struct repository *r, argv_array_push(&cmd.args, git_path_merge_msg(r)); if (opts->gpg_sign) argv_array_push(&cmd.args, opts->gpg_sign); + if (opts->ignore_date) + push_dates(&cmd, opts->committer_date_is_author_date); /* Add the tips to be merged */ for (j = to_merge; j; j = j->next) @@ -3763,7 +3909,9 @@ static int pick_commits(struct repository *r, setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || - opts->record_origin || opts->edit)); + opts->record_origin || opts->edit || + opts->committer_date_is_author_date || + opts->ignore_date)); if (read_and_refresh_cache(r, opts)) return -1; @@ -3914,7 +4062,7 @@ static int pick_commits(struct repository *r, item->commit, arg, item->arg_len, opts, res, 0); - } else if (check_todo && !res) { + } else if (is_rebase_i(opts) && check_todo && !res) { struct stat st; if (stat(get_todo_path(opts), &st)) { @@ -4215,8 +4363,10 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts) if (is_rebase_i(opts)) { if ((res = read_populate_todo(r, &todo_list, opts))) goto release_todo_list; - if (commit_staged_changes(r, opts, &todo_list)) - return -1; + if (commit_staged_changes(r, opts, &todo_list)) { + res = -1; + goto release_todo_list; + } } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(r); else if ((res = read_populate_todo(r, &todo_list, opts))) @@ -4430,7 +4580,6 @@ static const char *label_oid(struct object_id *oid, const char *label, struct labels_entry *labels_entry; struct string_entry *string_entry; struct object_id dummy; - size_t len; int i; string_entry = oidmap_get(&state->commit2label, oid); @@ -4450,10 +4599,10 @@ static const char *label_oid(struct object_id *oid, const char *label, * abbreviation for any uninteresting commit's names that does not * clash with any other label. */ + strbuf_reset(&state->buf); if (!label) { char *p; - strbuf_reset(&state->buf); strbuf_grow(&state->buf, GIT_MAX_HEXSZ); label = p = state->buf.buf; @@ -4476,32 +4625,55 @@ static const char *label_oid(struct object_id *oid, const char *label, p[i] = save; } } - } else if (((len = strlen(label)) == the_hash_algo->hexsz && - !get_oid_hex(label, &dummy)) || - (len == 1 && *label == '#') || - hashmap_get_from_hash(&state->labels, - strihash(label), label)) { + } else { + struct strbuf *buf = &state->buf; + /* - * If the label already exists, or if the label is a valid full - * OID, or the label is a '#' (which we use as a separator - * between merge heads and oneline), we append a dash and a - * number to make it unique. + * Sanitize labels by replacing non-alpha-numeric characters + * (including white-space ones) by dashes, as they might be + * illegal in file names (and hence in ref names). + * + * Note that we retain non-ASCII UTF-8 characters (identified + * via the most significant bit). They should be all acceptable + * in file names. We do not validate the UTF-8 here, that's not + * the job of this function. */ - struct strbuf *buf = &state->buf; + for (; *label; label++) + if ((*label & 0x80) || isalnum(*label)) + strbuf_addch(buf, *label); + /* avoid leading dash and double-dashes */ + else if (buf->len && buf->buf[buf->len - 1] != '-') + strbuf_addch(buf, '-'); + if (!buf->len) { + strbuf_addstr(buf, "rev-"); + strbuf_add_unique_abbrev(buf, oid, default_abbrev); + } + label = buf->buf; - strbuf_reset(buf); - strbuf_add(buf, label, len); + if ((buf->len == the_hash_algo->hexsz && + !get_oid_hex(label, &dummy)) || + (buf->len == 1 && *label == '#') || + hashmap_get_from_hash(&state->labels, + strihash(label), label)) { + /* + * If the label already exists, or if the label is a + * valid full OID, or the label is a '#' (which we use + * as a separator between merge heads and oneline), we + * append a dash and a number to make it unique. + */ + size_t len = buf->len; - for (i = 2; ; i++) { - strbuf_setlen(buf, len); - strbuf_addf(buf, "-%d", i); - if (!hashmap_get_from_hash(&state->labels, - strihash(buf->buf), - buf->buf)) - break; - } + for (i = 2; ; i++) { + strbuf_setlen(buf, len); + strbuf_addf(buf, "-%d", i); + if (!hashmap_get_from_hash(&state->labels, + strihash(buf->buf), + buf->buf)) + break; + } - label = buf->buf; + label = buf->buf; + } } FLEX_ALLOC_STR(labels_entry, label, label); @@ -4545,10 +4717,15 @@ static int make_script_with_merges(struct pretty_print_context *pp, strbuf_init(&state.buf, 32); if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) { + struct labels_entry *onto_label_entry; struct object_id *oid = &revs->cmdline.rev[0].item->oid; FLEX_ALLOC_STR(entry, string, "onto"); oidcpy(&entry->entry.oid, oid); oidmap_put(&state.commit2label, entry); + + FLEX_ALLOC_STR(onto_label_entry, label, "onto"); + hashmap_entry_init(&onto_label_entry->entry, strihash("onto")); + hashmap_add(&state.labels, &onto_label_entry->entry); } /* @@ -4603,10 +4780,6 @@ static int make_script_with_merges(struct pretty_print_context *pp, else strbuf_addbuf(&label, &oneline); - for (p1 = label.buf; *p1; p1++) - if (isspace(*p1)) - *(char *)p1 = '-'; - strbuf_reset(&buf); strbuf_addf(&buf, "%s -C %s", cmd_merge, oid_to_hex(&commit->object.oid)); @@ -5010,6 +5183,7 @@ static int skip_unnecessary_picks(struct repository *r, MOVE_ARRAY(todo_list->items, todo_list->items + i, todo_list->nr - i); todo_list->nr -= i; todo_list->current = 0; + todo_list->done_nr += i; if (is_fixup(peek_command(todo_list, 0))) record_in_rewritten(base_oid, peek_command(todo_list, 0)); @@ -5089,15 +5263,21 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return error_errno(_("could not write '%s'"), todo_file); } - todo_list_release(&new_todo); + res = -1; if (checkout_onto(r, opts, onto_name, &oid, orig_head)) - return -1; + goto cleanup; if (require_clean_work_tree(r, "rebase", "", 1, 1)) - return -1; + goto cleanup; - return sequencer_continue(r, opts); + todo_list_write_total_nr(&new_todo); + res = pick_commits(r, &new_todo, opts); + +cleanup: + todo_list_release(&new_todo); + + return res; } struct subject2item_entry { diff --git a/sequencer.h b/sequencer.h index 9f9ae291e3..e9a0e03ea2 100644 --- a/sequencer.h +++ b/sequencer.h @@ -43,6 +43,8 @@ struct replay_opts { int verbose; int quiet; int reschedule_failed_exec; + int committer_date_is_author_date; + int ignore_date; int mainline; diff --git a/sha1dc/sha1.c b/sha1dc/sha1.c index 63cd5c923a..dede2cbddf 100644 --- a/sha1dc/sha1.c +++ b/sha1dc/sha1.c @@ -98,7 +98,7 @@ /* * Defines Big Endian on a whitelist of OSs that are known to be Big * Endian-only. See - * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ + * https://lore.kernel.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/ */ #define SHA1DC_BIGENDIAN @@ -16,10 +16,10 @@ static int do_generic_cmd(const char *me, char *arg) setup_path(); if (!arg || !(arg = sq_dequote(arg)) || *arg == '-') die("bad argument"); - if (!starts_with(me, "git-")) + if (!skip_prefix(me, "git-", &me)) die("bad command"); - my_argv[0] = me + 4; + my_argv[0] = me; my_argv[1] = arg; my_argv[2] = NULL; diff --git a/submodule-config.c b/submodule-config.c index 401a9b2382..85064810b2 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -409,6 +409,13 @@ struct parse_config_parameter { int overwrite; }; +/* + * Parse a config item from .gitmodules. + * + * This does not handle submodule-related configuration from the main + * config store (.git/config, etc). Callers are responsible for + * checking for overrides in the main config store when appropriate. + */ static int parse_config(const char *var, const char *value, void *data) { struct parse_config_parameter *me = data; @@ -486,8 +493,9 @@ static int parse_config(const char *var, const char *value, void *data) warn_multiple_config(me->treeish_name, submodule->name, "update"); else if (parse_submodule_update_strategy(value, - &submodule->update_strategy) < 0) - die(_("invalid value for %s"), var); + &submodule->update_strategy) < 0 || + submodule->update_strategy.type == SM_UPDATE_COMMAND) + die(_("invalid value for %s"), var); } else if (!strcmp(item.buf, "shallow")) { if (!me->overwrite && submodule->recommend_shallow != -1) warn_multiple_config(me->treeish_name, submodule->name, @@ -618,7 +626,7 @@ static void submodule_cache_check_init(struct repository *repo) /* * Note: This function is private for a reason, the '.gitmodules' file should - * not be used as as a mechanism to retrieve arbitrary configuration stored in + * not be used as a mechanism to retrieve arbitrary configuration stored in * the repository. * * Runs the provided config function on the '.gitmodules' file found in the diff --git a/submodule.c b/submodule.c index 0f199c5137..9da7181321 100644 --- a/submodule.c +++ b/submodule.c @@ -1993,6 +1993,47 @@ out: return ret; } +int validate_submodule_git_dir(char *git_dir, const char *submodule_name) +{ + size_t len = strlen(git_dir), suffix_len = strlen(submodule_name); + char *p; + int ret = 0; + + if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' || + strcmp(p, submodule_name)) + BUG("submodule name '%s' not a suffix of git dir '%s'", + submodule_name, git_dir); + + /* + * We prevent the contents of sibling submodules' git directories to + * clash. + * + * Example: having a submodule named `hippo` and another one named + * `hippo/hooks` would result in the git directories + * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively, + * but the latter directory is already designated to contain the hooks + * of the former. + */ + for (; *p; p++) { + if (is_dir_sep(*p)) { + char c = *p; + + *p = '\0'; + if (is_git_directory(git_dir)) + ret = -1; + *p = c; + + if (ret < 0) + return error(_("submodule git dir '%s' is " + "inside git dir '%.*s'"), + git_dir, + (int)(p - git_dir), git_dir); + } + } + + return 0; +} + /* * Embeds a single submodules git directory into the superprojects git dir, * non recursively. @@ -2000,7 +2041,7 @@ out: static void relocate_single_git_dir_into_superproject(const char *path) { char *old_git_dir = NULL, *real_old_git_dir = NULL, *real_new_git_dir = NULL; - const char *new_git_dir; + char *new_git_dir; const struct submodule *sub; if (submodule_uses_worktrees(path)) @@ -2018,10 +2059,14 @@ static void relocate_single_git_dir_into_superproject(const char *path) if (!sub) die(_("could not lookup name for submodule '%s'"), path); - new_git_dir = git_path("modules/%s", sub->name); + new_git_dir = git_pathdup("modules/%s", sub->name); + if (validate_submodule_git_dir(new_git_dir, sub->name) < 0) + die(_("refusing to move '%s' into an existing git dir"), + real_old_git_dir); if (safe_create_leading_directories_const(new_git_dir) < 0) die(_("could not create directory '%s'"), new_git_dir); real_new_git_dir = real_pathdup(new_git_dir, 1); + free(new_git_dir); fprintf(stderr, _("Migrating git directory of '%s%s' from\n'%s' to\n'%s'\n"), get_super_prefix_or_empty(), path, diff --git a/submodule.h b/submodule.h index 8072e6d6dd..c81ec1a9b6 100644 --- a/submodule.h +++ b/submodule.h @@ -124,6 +124,11 @@ int push_unpushed_submodules(struct repository *r, */ int submodule_to_gitdir(struct strbuf *buf, const char *submodule); +/* + * Make sure that no submodule's git dir is nested in a sibling submodule's. + */ +int validate_submodule_git_dir(char *git_dir, const char *submodule_name); + #define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0) #define SUBMODULE_MOVE_HEAD_FORCE (1<<1) int submodule_move_head(const char *path, @@ -397,6 +397,10 @@ GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the built-in version of git-stash. See 'stash.useBuiltin' in git-config(1). +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the +built-in version of git add -i. See 'add.interactive.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the @@ -978,6 +982,15 @@ library for your script to use. output to the downstream---unlike the real version, it generates only up to 99 lines. + - test_bool_env <env-variable-name> <default-value> + + Given the name of an environment variable with a bool value, + normalize its value to a 0 (true) or 1 (false or empty string) + return code. Return with code corresponding to the given default + value if the variable is unset. + Abort the test script if either the value of the variable or the + default are not valid bool values. + Prerequisites ------------- diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c index f65e301f9d..7b4278462b 100644 --- a/t/helper/test-drop-caches.c +++ b/t/helper/test-drop-caches.c @@ -8,18 +8,21 @@ static int cmd_sync(void) { char Buffer[MAX_PATH]; DWORD dwRet; - char szVolumeAccessPath[] = "\\\\.\\X:"; + char szVolumeAccessPath[] = "\\\\.\\XXXX:"; HANDLE hVolWrite; - int success = 0; + int success = 0, dos_drive_prefix; dwRet = GetCurrentDirectory(MAX_PATH, Buffer); if ((0 == dwRet) || (dwRet > MAX_PATH)) return error("Error getting current directory"); - if (!has_dos_drive_prefix(Buffer)) + dos_drive_prefix = has_dos_drive_prefix(Buffer); + if (!dos_drive_prefix) return error("'%s': invalid drive letter", Buffer); - szVolumeAccessPath[4] = Buffer[0]; + memcpy(szVolumeAccessPath, Buffer, dos_drive_prefix); + szVolumeAccessPath[dos_drive_prefix] = '\0'; + hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (INVALID_HANDLE_VALUE == hVolWrite) diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 5d543ad21f..409034cf4e 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -185,6 +185,99 @@ static int cmp_by_st_size(const void *a, const void *b) return x > y ? -1 : (x < y ? +1 : 0); } +/* + * A very simple, reproducible pseudo-random generator. Copied from + * `test-genrandom.c`. + */ +static uint64_t my_random_value = 1234; + +static uint64_t my_random(void) +{ + my_random_value = my_random_value * 1103515245 + 12345; + return my_random_value; +} + +/* + * A fast approximation of the square root, without requiring math.h. + * + * It uses Newton's method to approximate the solution of 0 = x^2 - value. + */ +static double my_sqrt(double value) +{ + const double epsilon = 1e-6; + double x = value; + + if (value == 0) + return 0; + + for (;;) { + double delta = (value / x - x) / 2; + if (delta < epsilon && delta > -epsilon) + return x + delta; + x += delta; + } +} + +static int protect_ntfs_hfs_benchmark(int argc, const char **argv) +{ + size_t i, j, nr, min_len = 3, max_len = 20; + char **names; + int repetitions = 15, file_mode = 0100644; + uint64_t begin, end; + double m[3][2], v[3][2]; + uint64_t cumul; + double cumul2; + + if (argc > 1 && !strcmp(argv[1], "--with-symlink-mode")) { + file_mode = 0120000; + argc--; + argv++; + } + + nr = argc > 1 ? strtoul(argv[1], NULL, 0) : 1000000; + ALLOC_ARRAY(names, nr); + + if (argc > 2) { + min_len = strtoul(argv[2], NULL, 0); + if (argc > 3) + max_len = strtoul(argv[3], NULL, 0); + if (min_len > max_len) + die("min_len > max_len"); + } + + for (i = 0; i < nr; i++) { + size_t len = min_len + (my_random() % (max_len + 1 - min_len)); + + names[i] = xmallocz(len); + while (len > 0) + names[i][--len] = (char)(' ' + (my_random() % ('\x7f' - ' '))); + } + + for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) + for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) { + cumul = 0; + cumul2 = 0; + for (i = 0; i < repetitions; i++) { + begin = getnanotime(); + for (j = 0; j < nr; j++) + verify_path(names[j], file_mode); + end = getnanotime(); + printf("protect_ntfs = %d, protect_hfs = %d: %lfms\n", protect_ntfs, protect_hfs, (end-begin) / (double)1e6); + cumul += end - begin; + cumul2 += (end - begin) * (end - begin); + } + m[protect_ntfs][protect_hfs] = cumul / (double)repetitions; + v[protect_ntfs][protect_hfs] = my_sqrt(cumul2 / (double)repetitions - m[protect_ntfs][protect_hfs] * m[protect_ntfs][protect_hfs]); + printf("mean: %lfms, stddev: %lfms\n", m[protect_ntfs][protect_hfs] / (double)1e6, v[protect_ntfs][protect_hfs] / (double)1e6); + } + + for (protect_ntfs = 0; protect_ntfs < 2; protect_ntfs++) + for (protect_hfs = 0; protect_hfs < 2; protect_hfs++) + printf("ntfs=%d/hfs=%d: %lf%% slower\n", protect_ntfs, protect_hfs, (m[protect_ntfs][protect_hfs] - m[0][0]) * 100 / m[0][0]); + + return 0; +} + int cmd__path_utils(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { @@ -355,6 +448,26 @@ int cmd__path_utils(int argc, const char **argv) return !!res; } + if (argc > 1 && !strcmp(argv[1], "protect_ntfs_hfs")) + return !!protect_ntfs_hfs_benchmark(argc - 1, argv + 1); + + if (argc > 1 && !strcmp(argv[1], "is_valid_path")) { + int res = 0, expect = 1, i; + + for (i = 2; i < argc; i++) + if (!strcmp("--not", argv[i])) + expect = 0; + else if (expect != is_valid_path(argv[i])) + res = error("'%s' is%s a valid path", + argv[i], expect ? " not" : ""); + else + fprintf(stderr, + "'%s' is%s a valid path\n", + argv[i], expect ? "" : " not"); + + return !!res; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index ead6dc611a..1646aa25d8 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -18,8 +18,8 @@ #include "string-list.h" #include "thread-utils.h" #include "wildmatch.h" -#include <string.h> -#include <errno.h> +#include "gettext.h" +#include "parse-options.h" static int number_callbacks; static int parallel_next(struct child_process *cp, @@ -200,6 +200,174 @@ static int testsuite(int argc, const char **argv) return !!ret; } +static uint64_t my_random_next = 1234; + +static uint64_t my_random(void) +{ + uint64_t res = my_random_next; + my_random_next = my_random_next * 1103515245 + 12345; + return res; +} + +static int quote_stress_test(int argc, const char **argv) +{ + /* + * We are running a quote-stress test. + * spawn a subprocess that runs quote-stress with a + * special option that echoes back the arguments that + * were passed in. + */ + char special[] = ".?*\\^_\"'`{}()[]<>@~&+:;$%"; // \t\r\n\a"; + int i, j, k, trials = 100, skip = 0, msys2 = 0; + struct strbuf out = STRBUF_INIT; + struct argv_array args = ARGV_ARRAY_INIT; + struct option options[] = { + OPT_INTEGER('n', "trials", &trials, "Number of trials"), + OPT_INTEGER('s', "skip", &skip, "Skip <n> trials"), + OPT_BOOL('m', "msys2", &msys2, "Test quoting for MSYS2's sh"), + OPT_END() + }; + const char * const usage[] = { + "test-tool run-command quote-stress-test <options>", + NULL + }; + + argc = parse_options(argc, argv, NULL, options, usage, 0); + + setenv("MSYS_NO_PATHCONV", "1", 0); + + for (i = 0; i < trials; i++) { + struct child_process cp = CHILD_PROCESS_INIT; + size_t arg_count, arg_offset; + int ret = 0; + + argv_array_clear(&args); + if (msys2) + argv_array_pushl(&args, "sh", "-c", + "printf %s\\\\0 \"$@\"", "skip", NULL); + else + argv_array_pushl(&args, "test-tool", "run-command", + "quote-echo", NULL); + arg_offset = args.argc; + + if (argc > 0) { + trials = 1; + arg_count = argc; + for (j = 0; j < arg_count; j++) + argv_array_push(&args, argv[j]); + } else { + arg_count = 1 + (my_random() % 5); + for (j = 0; j < arg_count; j++) { + char buf[20]; + size_t min_len = 1; + size_t arg_len = min_len + + (my_random() % (ARRAY_SIZE(buf) - min_len)); + + for (k = 0; k < arg_len; k++) + buf[k] = special[my_random() % + ARRAY_SIZE(special)]; + buf[arg_len] = '\0'; + + argv_array_push(&args, buf); + } + } + + if (i < skip) + continue; + + cp.argv = args.argv; + strbuf_reset(&out); + if (pipe_command(&cp, NULL, 0, &out, 0, NULL, 0) < 0) + return error("Failed to spawn child process"); + + for (j = 0, k = 0; j < arg_count; j++) { + const char *arg = args.argv[j + arg_offset]; + + if (strcmp(arg, out.buf + k)) + ret = error("incorrectly quoted arg: '%s', " + "echoed back as '%s'", + arg, out.buf + k); + k += strlen(out.buf + k) + 1; + } + + if (k != out.len) + ret = error("got %d bytes, but consumed only %d", + (int)out.len, (int)k); + + if (ret) { + fprintf(stderr, "Trial #%d failed. Arguments:\n", i); + for (j = 0; j < arg_count; j++) + fprintf(stderr, "arg #%d: '%s'\n", + (int)j, args.argv[j + arg_offset]); + + strbuf_release(&out); + argv_array_clear(&args); + + return ret; + } + + if (i && (i % 100) == 0) + fprintf(stderr, "Trials completed: %d\n", (int)i); + } + + strbuf_release(&out); + argv_array_clear(&args); + + return 0; +} + +static int quote_echo(int argc, const char **argv) +{ + while (argc > 1) { + fwrite(argv[1], strlen(argv[1]), 1, stdout); + fputc('\0', stdout); + argv++; + argc--; + } + + return 0; +} + +static int inherit_handle(const char *argv0) +{ + struct child_process cp = CHILD_PROCESS_INIT; + char path[PATH_MAX]; + int tmp; + + /* First, open an inheritable handle */ + xsnprintf(path, sizeof(path), "out-XXXXXX"); + tmp = xmkstemp(path); + + argv_array_pushl(&cp.args, + "test-tool", argv0, "inherited-handle-child", NULL); + cp.in = -1; + cp.no_stdout = cp.no_stderr = 1; + if (start_command(&cp) < 0) + die("Could not start child process"); + + /* Then close it, and try to delete it. */ + close(tmp); + if (unlink(path)) + die("Could not delete '%s'", path); + + if (close(cp.in) < 0 || finish_command(&cp) < 0) + die("Child did not finish"); + + return 0; +} + +static int inherit_handle_child(void) +{ + struct strbuf buf = STRBUF_INIT; + + if (strbuf_read(&buf, 0, 0) < 0) + die("Could not read stdin"); + printf("Received %s\n", buf.buf); + strbuf_release(&buf); + + return 0; +} + int cmd__run_command(int argc, const char **argv) { struct child_process proc = CHILD_PROCESS_INIT; @@ -207,6 +375,16 @@ int cmd__run_command(int argc, const char **argv) if (argc > 1 && !strcmp(argv[1], "testsuite")) exit(testsuite(argc - 1, argv + 1)); + if (!strcmp(argv[1], "inherited-handle")) + exit(inherit_handle(argv[0])); + if (!strcmp(argv[1], "inherited-handle-child")) + exit(inherit_handle_child()); + + if (argc >= 2 && !strcmp(argv[1], "quote-stress-test")) + return !!quote_stress_test(argc - 1, argv + 1); + + if (argc >= 2 && !strcmp(argv[1], "quote-echo")) + return !!quote_echo(argc - 1, argv + 1); if (argc < 3) return 1; diff --git a/t/lib-bash.sh b/t/lib-bash.sh index 2be955fafb..b0b6060929 100644 --- a/t/lib-bash.sh +++ b/t/lib-bash.sh @@ -2,10 +2,12 @@ # to run under Bash; primarily intended for tests of the completion # script. -if test -n "$BASH" && test -z "$POSIXLY_CORRECT"; then +if test -n "$BASH" && test -z "$POSIXLY_CORRECT" +then # we are in full-on bash mode true -elif type bash >/dev/null 2>&1; then +elif type bash >/dev/null 2>&1 +then # execute in full-on bash mode unset POSIXLY_CORRECT exec bash "$0" "$@" diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index fb8f887080..e62569222b 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -15,7 +15,7 @@ # # test_done -if ! git env--helper --type=bool --default=true --exit-code GIT_TEST_GIT_DAEMON +if ! test_bool_env GIT_TEST_GIT_DAEMON true then skip_all="git-daemon testing disabled (unset GIT_TEST_GIT_DAEMON to enable)" test_done diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index bc0b9c71f8..7d248e6588 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -69,7 +69,7 @@ svn_cmd () { maybe_start_httpd () { loc=${1-svn} - if git env--helper --type=bool --default=false --exit-code GIT_TEST_SVN_HTTPD + if test_bool_env GIT_TEST_SVN_HTTPD false then . "$TEST_DIRECTORY"/lib-httpd.sh LIB_HTTPD_SVN="$loc" @@ -104,7 +104,7 @@ EOF } require_svnserve () { - if ! git env--helper --type=bool --default=false --exit-code GIT_TEST_SVNSERVE + if ! test_bool_env GIT_TEST_SVNSERVE false then skip_all='skipping svnserve test. (set $GIT_TEST_SVNSERVE to enable)' test_done diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 0d985758c6..656997b4d6 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -41,7 +41,7 @@ then test_done fi -if ! git env--helper --type=bool --default=true --exit-code GIT_TEST_HTTPD +if ! test_bool_env GIT_TEST_HTTPD true then skip_all="Network testing disabled (unset GIT_TEST_HTTPD to enable)" test_done diff --git a/t/lib-httpd/apply-one-time-sed.sh b/t/lib-httpd/apply-one-time-sed.sh index fcef728925..bf7689d020 100644 --- a/t/lib-httpd/apply-one-time-sed.sh +++ b/t/lib-httpd/apply-one-time-sed.sh @@ -7,11 +7,13 @@ # # This can be used to simulate the effects of the repository changing in # between HTTP request-response pairs. -if [ -e one-time-sed ]; then +if test -f one-time-sed +then "$GIT_EXEC_PATH/git-http-backend" >out - sed "$(cat one-time-sed)" <out >out_modified + sed "$(cat one-time-sed)" out >out_modified - if diff out out_modified >/dev/null; then + if cmp -s out out_modified + then cat out else cat out_modified diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 66554d2161..14e4cda287 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -4,7 +4,6 @@ use lib '../../perl/build/lib'; use strict; use warnings; use Getopt::Long; -use Git; use Cwd qw(realpath); sub get_times { @@ -85,6 +84,11 @@ sub format_size { return $out; } +sub sane_backticks { + open(my $fh, '-|', @_); + return <$fh>; +} + my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); @@ -102,7 +106,8 @@ while (scalar @ARGV) { my $prefix = ''; last if -f $arg or $arg eq "--"; if (! -d $arg) { - my $rev = Git::command_oneline(qw(rev-parse --verify), $arg); + my $rev = sane_backticks(qw(git rev-parse --verify), $arg); + chomp $rev; $dir = "build/".$rev; } elsif ($arg eq '.') { $dir = '.'; @@ -219,13 +224,7 @@ sub print_default_results { for my $i (0..$#dirs) { my $d = $dirs[$i]; my $base = "$resultsdir/$prefixes{$d}$t"; - $times{$prefixes{$d}.$t} = []; - foreach my $type (qw(times size)) { - if (-e "$base.$type") { - $times{$prefixes{$d}.$t} = [get_times("$base.$type")]; - last; - } - } + $times{$prefixes{$d}.$t} = [get_times("$base.result")]; my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; my $w = length format_times($r,$u,$s,$firstr); $colwidth[$i] = $w if $w > $colwidth[$i]; @@ -267,7 +266,7 @@ sub print_sorted_results { my ($prevr, $prevu, $prevs, $prevrev); for my $i (0..$#dirs) { my $d = $dirs[$i]; - my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result"); if ($i > 0 and defined $r and defined $prevr and $prevr > 0) { my $percent = 100.0 * ($r - $prevr) / $prevr; push @evolutions, { "percent" => $percent, @@ -327,7 +326,7 @@ sub print_codespeed_results { my $commitid = $prefixes{$d}; $commitid =~ s/^build_//; $commitid =~ s/\.$//; - my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.result"); my %vals = ( "commitid" => $commitid, diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh index a369152c47..7ee791669a 100755 --- a/t/perf/p5303-many-packs.sh +++ b/t/perf/p5303-many-packs.sh @@ -85,4 +85,22 @@ do ' done +# Measure pack loading with 10,000 packs. +test_expect_success 'generate lots of packs' ' + for i in $(test_seq 10000); do + echo "blob" + echo "data <<EOF" + echo "blob $i" + echo "EOF" + echo "checkpoint" + done | + git -c fastimport.unpackLimit=0 fast-import +' + +# The purpose of this test is to evaluate load time for a large number +# of packs while doing as little other work as possible. +test_perf "load 10,000 packs" ' + git rev-parse --verify "HEAD^{commit}" +' + test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index b58a43ea43..13e389367a 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -214,7 +214,7 @@ test_perf_ () { else test_ok_ "$1" fi - "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times + "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".result } test_perf () { @@ -223,7 +223,7 @@ test_perf () { test_size_ () { say >&3 "running: $2" - if test_eval_ "$2" 3>"$base".size; then + if test_eval_ "$2" 3>"$base".result; then test_ok_ "$1" else test_failure_ "$@" diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 7aabde1a69..8a81a249d0 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -20,9 +20,9 @@ modification *should* take notice and update the test vectors here. . ./test-lib.sh -try_local_x () { - local x="local" && - echo "$x" +try_local_xy () { + local x="local" y="alsolocal" && + echo "$x $y" } # Check whether the shell supports the "local" keyword. "local" is not @@ -35,11 +35,12 @@ try_local_x () { # relying on "local". test_expect_success 'verify that the running shell supports "local"' ' x="notlocal" && - echo "local" >expected1 && - try_local_x >actual1 && + y="alsonotlocal" && + echo "local alsolocal" >expected1 && + try_local_xy >actual1 && test_cmp expected1 actual1 && - echo "notlocal" >expected2 && - echo "$x" >actual2 && + echo "notlocal alsonotlocal" >expected2 && + echo "$x $y" >actual2 && test_cmp expected2 actual2 ' @@ -916,6 +917,40 @@ test_expect_success 'test_oid can look up data for SHA-256' ' test "$hexsz" -eq 64 ' +test_expect_success 'test_bool_env' ' + ( + sane_unset envvar && + + test_bool_env envvar true && + ! test_bool_env envvar false && + + envvar= && + export envvar && + ! test_bool_env envvar true && + ! test_bool_env envvar false && + + envvar=true && + test_bool_env envvar true && + test_bool_env envvar false && + + envvar=false && + ! test_bool_env envvar true && + ! test_bool_env envvar false && + + envvar=invalid && + # When encountering an invalid bool value, test_bool_env + # prints its error message to the original stderr of the + # test script, hence the redirection of fd 7, and aborts + # with "exit 1", hence the subshell. + ! ( test_bool_env envvar true ) 7>err && + grep "error: test_bool_env requires bool values" err && + + envvar=true && + ! ( test_bool_env envvar invalid ) 7>err && + grep "error: test_bool_env requires bool values" err + ) +' + ################################################################ # Basics of the basics diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 2694c81afd..8d3d9144c0 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -38,8 +38,8 @@ test_expect_success 'looping aliases - internal execution' ' #' test_expect_success 'run-command formats empty args properly' ' - GIT_TRACE=1 git frotz a "" b " " c 2>&1 | - sed -ne "/run_command:/s/.*trace: run_command: //p" >actual && + test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw && + sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual && echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect && test_cmp expect actual ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index c954c709ad..6c6d77b51a 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -35,7 +35,7 @@ filter_git () { # Compare two files and ensure that `clean` and `smudge` respectively are # called at least once if specified in the `expect` file. The actual # invocation count is not relevant because their number can vary. -# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +# c.f. http://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ test_cmp_count () { expect=$1 actual=$2 @@ -50,7 +50,7 @@ test_cmp_count () { # Compare two files but exclude all `clean` invocations because Git can # call `clean` zero or more times. -# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +# c.f. http://lore.kernel.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ test_cmp_exclude_clean () { expect=$1 actual=$2 diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 553e94bc5a..de2df573a7 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -165,6 +165,15 @@ test_expect_success 'absolute path rejects the empty string' ' test_must_fail test-tool path-utils absolute_path "" ' +test_expect_success MINGW '<drive-letter>:\\abc is an absolute path' ' + for letter in : \" C Z 1 ä + do + path=$letter:\\abc && + absolute="$(test-tool path-utils absolute_path "$path")" && + test "$path" = "$absolute" || return 1 + done +' + test_expect_success 'real path rejects the empty string' ' test_must_fail test-tool path-utils real_path "" ' @@ -427,6 +436,9 @@ test_expect_success 'match .gitmodules' ' ~1000000 \ ~9999999 \ \ + .gitmodules:\$DATA \ + "gitmod~4 . :\$DATA" \ + \ --not \ ".gitmodules x" \ ".gitmodules .x" \ @@ -451,7 +463,25 @@ test_expect_success 'match .gitmodules' ' \ GI7EB~1 \ GI7EB~01 \ - GI7EB~1X + GI7EB~1X \ + \ + .gitmodules,:\$DATA +' + +test_expect_success MINGW 'is_valid_path() on Windows' ' + test-tool path-utils is_valid_path \ + win32 \ + "win32 x" \ + ../hello.txt \ + C:\\git \ + \ + --not \ + "win32 " \ + "win32 /x " \ + "win32." \ + "win32 . ." \ + .../hello.txt \ + colon:test ' test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 17c9c0f3bb..7d599675e3 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -12,6 +12,10 @@ cat >hello-script <<-EOF cat hello-script EOF +test_expect_success MINGW 'subprocess inherits only std handles' ' + test-tool run-command inherited-handle +' + test_expect_success 'start_command reports ENOENT (slash)' ' test-tool run-command start-command-ENOENT ./does-not-exist 2>err && test_i18ngrep "\./does-not-exist" err diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index ce9a4a5f32..5a633690bf 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -21,9 +21,10 @@ generate_expected_cache_tree_rec () { parent="$2" && # ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux # We want to count only foo because it's the only direct child - subtrees=$(git ls-files|grep /|cut -d / -f 1|uniq) && + git ls-files >files && + subtrees=$(grep / files|cut -d / -f 1|uniq) && subtree_count=$(echo "$subtrees"|awk -v c=0 '$1 != "" {++c} END {print c}') && - entries=$(git ls-files|wc -l) && + entries=$(wc -l <files) && printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" && for subtree in $subtrees do diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index ba71b159ba..eb44bafb59 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -215,7 +215,6 @@ test_expect_success 'read-tree adds to worktree, dirty case' ' ' test_expect_success 'index removal and worktree narrowing at the same time' ' - >empty && echo init.t >.git/info/sparse-checkout && echo sub/added >>.git/info/sparse-checkout && git checkout -f top && @@ -223,7 +222,7 @@ test_expect_success 'index removal and worktree narrowing at the same time' ' git checkout removed && git ls-files sub/added >result && test ! -f sub/added && - test_cmp empty result + test_must_be_empty result ' test_expect_success 'read-tree --reset removes outside worktree' ' diff --git a/t/t1014-read-tree-confusing.sh b/t/t1014-read-tree-confusing.sh index 2f5a25d503..da3376b3bb 100755 --- a/t/t1014-read-tree-confusing.sh +++ b/t/t1014-read-tree-confusing.sh @@ -49,6 +49,7 @@ git~1 .git.SPACE .git.{space} .\\\\.GIT\\\\foobar backslashes .git\\\\foobar backslashes2 +.git...:alternate-stream EOF test_expect_success 'utf-8 paths allowed with core.protectHFS off' ' diff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh index 3a0de0ddaa..ebb8e1aecb 100755 --- a/t/t1309-early-config.sh +++ b/t/t1309-early-config.sh @@ -29,7 +29,7 @@ test_expect_success 'ceiling' ' cd sub && test-tool config read_early_config early.config ) >output && - test -z "$(cat output)" + test_must_be_empty output ' test_expect_success 'ceiling #2' ' diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 69a7f27311..b815cdd1b8 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -380,13 +380,13 @@ test_expect_success 'Query "master@{May 26 2005 23:32:00}" (exactly history star test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && test $C = $(cat o) && - test "" = "$(cat e)" + test_must_be_empty e ' test_expect_success 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' ' test_when_finished "rm -f o e" && git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e && test $A = $(cat o) && - test "" = "$(cat e)" + test_must_be_empty e ' test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' ' test_when_finished "rm -f o e" && @@ -398,13 +398,13 @@ test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' ' test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && test $Z = $(cat o) && - test "" = "$(cat e)" + test_must_be_empty e ' test_expect_success 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' ' test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && test $E = $(cat o) && - test "" = "$(cat e)" + test_must_be_empty e ' test_expect_success 'Query "master@{2005-05-28}" (past end of history)' ' test_when_finished "rm -f o e" && diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 82950c0282..76d9b744a6 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -195,7 +195,7 @@ test_expect_success 'delete' ' git reflog delete master@{1} && git reflog show master > output && - test $(($master_entry_count - 1)) = $(wc -l < output) && + test_line_count = $(($master_entry_count - 1)) output && test $HEAD_entry_count = $(git reflog | wc -l) && ! grep ox < output && @@ -209,7 +209,7 @@ test_expect_success 'delete' ' git reflog delete master@{07.04.2005.15:15:00.-0700} && git reflog show master > output && - test $(($master_entry_count - 1)) = $(wc -l < output) && + test_line_count = $(($master_entry_count - 1)) output && ! grep dragon < output ' diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 7c7ff7e961..02478bc4ec 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -441,6 +441,7 @@ while read name path pretty; do ( git init $name-$type && cd $name-$type && + git config core.protectNTFS false && echo content >file && git add file && git commit -m base && diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 0177fd815c..603019b541 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -146,6 +146,16 @@ test_expect_success 'rev-parse --show-object-format in repo' ' grep "unknown mode for --show-object-format: squeamish-ossifrage" err ' +test_expect_success '--show-toplevel from subdir of working tree' ' + pwd >expect && + git -C sub/dir rev-parse --show-toplevel >actual && + test_cmp expect actual +' + +test_expect_success '--show-toplevel from inside .git' ' + test_must_fail git -C .git rev-parse --show-toplevel +' + test_expect_success 'showing the superproject correctly' ' git rev-parse --show-superproject-working-tree >out && test_must_be_empty out && diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 624d0a588f..6d951ca015 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -138,10 +138,10 @@ test_expect_success 'incorrect file in :path and :N:path' ' test_expect_success 'invalid @{n} reference' ' test_must_fail git rev-parse master@{99999} >output 2>error && - test -z "$(cat output)" && + test_must_be_empty output && grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error && test_must_fail git rev-parse --verify master@{99999} >output 2>error && - test -z "$(cat output)" && + test_must_be_empty output && grep "fatal: Log for [^ ]* only has [0-9][0-9]* entries." error ' @@ -155,13 +155,13 @@ test_expect_success 'relative path not found' ' test_expect_success 'relative path outside worktree' ' test_must_fail git rev-parse HEAD:../file.txt >output 2>error && - test -z "$(cat output)" && + test_must_be_empty output && test_i18ngrep "outside repository" error ' test_expect_success 'relative path when cwd is outside worktree' ' test_must_fail git --git-dir=.git --work-tree=subdir rev-parse HEAD:./file.txt >output 2>error && - test -z "$(cat output)" && + test_must_be_empty output && grep "relative path syntax can.t be used outside working tree." error ' diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 25744e270a..18fa6cf40d 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -282,7 +282,7 @@ test_expect_success 'rev-parse --disambiguate' ' # commits created by commit-tree in earlier tests share a # different prefix. git rev-parse --disambiguate=000000000 >actual && - test $(wc -l <actual) = 16 && + test_line_count = 16 actual && test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000 ' diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 8a9831413c..b5ece19460 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -438,7 +438,7 @@ test_expect_success 'git worktree add does not match remote' ' cd foo && test_must_fail git config "branch.foo.remote" && test_must_fail git config "branch.foo.merge" && - ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo ) ' @@ -483,7 +483,7 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' ' cd foo && test_must_fail git config "branch.foo.remote" && test_must_fail git config "branch.foo.merge" && - ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + test_cmp_rev ! refs/remotes/repo_a/foo refs/heads/foo ) ' diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index 0579cd9969..ec2b456dbb 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -8,8 +8,8 @@ test_description='range-diff tests' # harm than good. We need some real history. test_expect_success 'setup' ' - git fast-import < "$TEST_DIRECTORY"/t3206/history.export && - test_oid_cache <<-EOF + git fast-import <"$TEST_DIRECTORY"/t3206/history.export && + test_oid_cache <<-\EOF # topic t1 sha1:4de457d t2 sha1:fccce22 @@ -121,88 +121,88 @@ test_expect_success 'setup' ' test_expect_success 'simple A..B A..C (unmodified)' ' git range-diff --no-color master..topic master..unmodified \ >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid u1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid u2) s/4/A/ 3: $(test_oid t3) = 3: $(test_oid u3) s/11/B/ 4: $(test_oid t4) = 4: $(test_oid u4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'simple B...C (unmodified)' ' git range-diff --no-color topic...unmodified >actual && - # same "expected" as above - test_cmp expected actual + # same "expect" as above + test_cmp expect actual ' test_expect_success 'simple A B C (unmodified)' ' git range-diff --no-color master topic unmodified >actual && - # same "expected" as above - test_cmp expected actual + # same "expect" as above + test_cmp expect actual ' test_expect_success 'trivial reordering' ' git range-diff --no-color master topic reordered >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid r1) s/5/A/ 3: $(test_oid t3) = 2: $(test_oid r2) s/11/B/ 4: $(test_oid t4) = 3: $(test_oid r3) s/12/B/ 2: $(test_oid t2) = 4: $(test_oid r4) s/4/A/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'removed a commit' ' git range-diff --no-color master topic removed >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid d1) s/5/A/ 2: $(test_oid t2) < -: $(test_oid __) s/4/A/ 3: $(test_oid t3) = 2: $(test_oid d2) s/11/B/ 4: $(test_oid t4) = 3: $(test_oid d3) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'added a commit' ' git range-diff --no-color master topic added >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid a1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid a2) s/4/A/ -: $(test_oid __) > 3: $(test_oid a3) s/6/A/ 3: $(test_oid t3) = 4: $(test_oid a4) s/11/B/ 4: $(test_oid t4) = 5: $(test_oid a5) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'new base, A B C' ' git range-diff --no-color master topic rebased >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid b1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid b2) s/4/A/ 3: $(test_oid t3) = 3: $(test_oid b3) s/11/B/ 4: $(test_oid t4) = 4: $(test_oid b4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'new base, B...C' ' # this syntax includes the commits from master! git range-diff --no-color topic...rebased >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && -: $(test_oid __) > 1: $(test_oid b5) unrelated 1: $(test_oid t1) = 2: $(test_oid b1) s/5/A/ 2: $(test_oid t2) = 3: $(test_oid b2) s/4/A/ 3: $(test_oid t3) = 4: $(test_oid b3) s/11/B/ 4: $(test_oid t4) = 5: $(test_oid b4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'changed commit' ' git range-diff --no-color topic...changed >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid c1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid c2) s/4/A/ 3: $(test_oid t3) ! 3: $(test_oid c3) s/11/B/ @@ -226,23 +226,23 @@ test_expect_success 'changed commit' ' +B 13 EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'changed commit with --no-patch diff option' ' git range-diff --no-color --no-patch topic...changed >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid c1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid c2) s/4/A/ 3: $(test_oid t3) ! 3: $(test_oid c3) s/11/B/ 4: $(test_oid t4) ! 4: $(test_oid c4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'changed commit with --stat diff option' ' git range-diff --no-color --stat topic...changed >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid c1) s/5/A/ a => b | 0 1 file changed, 0 insertions(+), 0 deletions(-) @@ -256,12 +256,12 @@ test_expect_success 'changed commit with --stat diff option' ' a => b | 0 1 file changed, 0 insertions(+), 0 deletions(-) EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'changed commit with sm config' ' git range-diff --no-color --submodule=log topic...changed >actual && - cat >expected <<-EOF && + cat >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid c1) s/5/A/ 2: $(test_oid t2) = 2: $(test_oid c2) s/4/A/ 3: $(test_oid t3) ! 3: $(test_oid c3) s/11/B/ @@ -285,12 +285,12 @@ test_expect_success 'changed commit with sm config' ' +B 13 EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'renamed file' ' git range-diff --no-color --submodule=log topic...renamed-file >actual && - sed s/Z/\ /g >expected <<-EOF && + sed s/Z/\ /g >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid n1) s/5/A/ 2: $(test_oid t2) ! 2: $(test_oid n2) s/4/A/ @@ Metadata @@ -330,12 +330,12 @@ test_expect_success 'renamed file' ' Z 10 Z B EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'file with mode only change' ' git range-diff --no-color --submodule=log topic...mode-only-change >actual && - sed s/Z/\ /g >expected <<-EOF && + sed s/Z/\ /g >expect <<-EOF && 1: fccce22 ! 1: 4d39cb3 s/4/A/ @@ Metadata ZAuthor: Thomas Rast <trast@inf.ethz.ch> @@ -370,12 +370,12 @@ test_expect_success 'file with mode only change' ' + ## other-file (mode change 100644 => 100755) ## 3: a63e992 = 3: 4c1e0f5 s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'file added and later removed' ' git range-diff --no-color --submodule=log topic...added-removed >actual && - sed s/Z/\ /g >expected <<-EOF && + sed s/Z/\ /g >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid s1) s/5/A/ 2: $(test_oid t2) ! 2: $(test_oid s2) s/4/A/ @@ Metadata @@ -411,7 +411,7 @@ test_expect_success 'file added and later removed' ' + ## new-file (deleted) ## 4: $(test_oid t4) = 4: $(test_oid s4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'no commits on one side' ' @@ -421,7 +421,7 @@ test_expect_success 'no commits on one side' ' test_expect_success 'changed message' ' git range-diff --no-color topic...changed-message >actual && - sed s/Z/\ /g >expected <<-EOF && + sed s/Z/\ /g >expect <<-EOF && 1: $(test_oid t1) = 1: $(test_oid m1) s/5/A/ 2: $(test_oid t2) ! 2: $(test_oid m2) s/4/A/ @@ Metadata @@ -436,7 +436,7 @@ test_expect_success 'changed message' ' 3: $(test_oid t3) = 3: $(test_oid m3) s/11/B/ 4: $(test_oid t4) = 4: $(test_oid m4) s/12/B/ EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'dual-coloring' ' @@ -505,4 +505,202 @@ test_expect_success 'range-diff overrides diff.noprefix internally' ' git -c diff.noprefix=true range-diff HEAD^... ' +test_expect_success 'range-diff compares notes by default' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + git range-diff --no-color master..topic master..unmodified \ + >actual && + sed s/Z/\ /g >expect <<-EOF && + 1: $(test_oid t1) = 1: $(test_oid u1) s/5/A/ + 2: $(test_oid t2) = 2: $(test_oid u2) s/4/A/ + 3: $(test_oid t3) = 3: $(test_oid u3) s/11/B/ + 4: $(test_oid t4) ! 4: $(test_oid u4) s/12/B/ + @@ Commit message + Z + Z + Z ## Notes ## + - topic note + + unmodified note + Z + Z ## file ## + Z@@ file: A + EOF + test_cmp expect actual +' + +test_expect_success 'range-diff with --no-notes' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + git range-diff --no-color --no-notes master..topic master..unmodified \ + >actual && + cat >expect <<-EOF && + 1: $(test_oid t1) = 1: $(test_oid u1) s/5/A/ + 2: $(test_oid t2) = 2: $(test_oid u2) s/4/A/ + 3: $(test_oid t3) = 3: $(test_oid u3) s/11/B/ + 4: $(test_oid t4) = 4: $(test_oid u4) s/12/B/ + EOF + test_cmp expect actual +' + +test_expect_success 'range-diff with multiple --notes' ' + git notes --ref=note1 add -m "topic note1" topic && + git notes --ref=note1 add -m "unmodified note1" unmodified && + test_when_finished git notes --ref=note1 remove topic unmodified && + git notes --ref=note2 add -m "topic note2" topic && + git notes --ref=note2 add -m "unmodified note2" unmodified && + test_when_finished git notes --ref=note2 remove topic unmodified && + git range-diff --no-color --notes=note1 --notes=note2 master..topic master..unmodified \ + >actual && + sed s/Z/\ /g >expect <<-EOF && + 1: $(test_oid t1) = 1: $(test_oid u1) s/5/A/ + 2: $(test_oid t2) = 2: $(test_oid u2) s/4/A/ + 3: $(test_oid t3) = 3: $(test_oid u3) s/11/B/ + 4: $(test_oid t4) ! 4: $(test_oid u4) s/12/B/ + @@ Commit message + Z + Z + Z ## Notes (note1) ## + - topic note1 + + unmodified note1 + Z + Z + Z ## Notes (note2) ## + - topic note2 + + unmodified note2 + Z + Z ## file ## + Z@@ file: A + EOF + test_cmp expect actual +' + +test_expect_success 'format-patch --range-diff does not compare notes by default' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + git format-patch --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "= 4: .* s/12/B" 0000-* && + ! grep "Notes" 0000-* && + ! grep "note" 0000-* +' + +test_expect_success 'format-patch --range-diff with --no-notes' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + git format-patch --no-notes --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "= 4: .* s/12/B" 0000-* && + ! grep "Notes" 0000-* && + ! grep "note" 0000-* +' + +test_expect_success 'format-patch --range-diff with --notes' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + git format-patch --notes --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "! 4: .* s/12/B" 0000-* && + sed s/Z/\ /g >expect <<-EOF && + @@ Commit message + Z + Z + Z ## Notes ## + - topic note + + unmodified note + Z + Z ## file ## + Z@@ file: A + EOF + sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual && + test_cmp expect actual +' + +test_expect_success 'format-patch --range-diff with --notes' ' + git notes add -m "topic note" topic && + git notes add -m "unmodified note" unmodified && + test_when_finished git notes remove topic unmodified && + test_config format.notes true && + git format-patch --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "! 4: .* s/12/B" 0000-* && + sed s/Z/\ /g >expect <<-EOF && + @@ Commit message + Z + Z + Z ## Notes ## + - topic note + + unmodified note + Z + Z ## file ## + Z@@ file: A + EOF + sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual && + test_cmp expect actual +' + +test_expect_success 'format-patch --range-diff with multiple notes' ' + git notes --ref=note1 add -m "topic note1" topic && + git notes --ref=note1 add -m "unmodified note1" unmodified && + test_when_finished git notes --ref=note1 remove topic unmodified && + git notes --ref=note2 add -m "topic note2" topic && + git notes --ref=note2 add -m "unmodified note2" unmodified && + test_when_finished git notes --ref=note2 remove topic unmodified && + git format-patch --notes=note1 --notes=note2 --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "! 4: .* s/12/B" 0000-* && + sed s/Z/\ /g >expect <<-EOF && + @@ Commit message + Z + Z + Z ## Notes (note1) ## + - topic note1 + + unmodified note1 + Z + Z + Z ## Notes (note2) ## + - topic note2 + + unmodified note2 + Z + Z ## file ## + Z@@ file: A + EOF + sed "/@@ Commit message/,/@@ file: A/!d" 0000-* >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index d66a5f6faa..8f43303007 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -54,7 +54,9 @@ test_expect_success 'create notes' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b4" = "$(git notes show)" && + echo b4 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' @@ -79,14 +81,21 @@ test_expect_success 'edit existing notes' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b3" = "$(git notes show)" && + echo b3 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' test_expect_success 'show notes from treeish' ' - test "b3" = "$(git notes --ref commits^{tree} show)" && - test "b4" = "$(git notes --ref commits@{1} show)" + echo b3 >expect && + git notes --ref commits^{tree} show >actual && + test_cmp expect actual && + + echo b4 >expect && + git notes --ref commits@{1} show >actual && + test_cmp expect actual ' test_expect_success 'cannot edit notes from non-ref' ' @@ -99,7 +108,9 @@ test_expect_success 'cannot "git notes add -m" where notes already exists' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b3" = "$(git notes show)" && + echo b3 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' @@ -109,7 +120,9 @@ test_expect_success 'can overwrite existing note with "git notes add -f -m"' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b1" = "$(git notes show)" && + echo b1 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' @@ -119,7 +132,9 @@ test_expect_success 'add w/no options on existing note morphs into edit' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b2" = "$(git notes show)" && + echo b2 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' @@ -129,7 +144,9 @@ test_expect_success 'can overwrite existing note with "git notes add -f"' ' test_path_is_missing .git/NOTES_EDITMSG && git ls-tree -r refs/notes/commits >actual && test_line_count = 1 actual && - test "b1" = "$(git notes show)" && + echo b1 >expect && + git notes show >actual && + test_cmp expect actual && git show HEAD^ && test_must_fail git notes show HEAD^ ' @@ -146,7 +163,8 @@ test_expect_success 'show notes' ' Notes: ${indent}b1 EOF - ! (git cat-file commit HEAD | grep b1) && + git cat-file commit HEAD >commits && + ! grep b1 commits && git log -1 >actual && test_cmp expect actual ' @@ -472,9 +490,11 @@ test_expect_success 'removing with --stdin --ignore-missing' ' test_expect_success 'list notes with "git notes list"' ' commit_2=$(git rev-parse 2nd) && commit_3=$(git rev-parse 3rd) && + note_2=$(git rev-parse refs/notes/commits:$commit_2) && + note_3=$(git rev-parse refs/notes/commits:$commit_3) && sort -t" " -k2 >expect <<-EOF && - $(git rev-parse refs/notes/commits:$commit_2) $commit_2 - $(git rev-parse refs/notes/commits:$commit_3) $commit_3 + $note_2 $commit_2 + $note_3 $commit_3 EOF git notes list >actual && test_cmp expect actual @@ -486,9 +506,7 @@ test_expect_success 'list notes with "git notes"' ' ' test_expect_success 'list specific note with "git notes list <object>"' ' - cat >expect <<-EOF && - $(git rev-parse refs/notes/commits:$commit_3) - EOF + git rev-parse refs/notes/commits:$commit_3 >expect && git notes list HEAD^^ >actual && test_cmp expect actual ' @@ -512,10 +530,11 @@ test_expect_success 'append to existing note with "git notes append"' ' test_expect_success '"git notes list" does not expand to "git notes list HEAD"' ' commit_5=$(git rev-parse 5th) && + note_5=$(git rev-parse refs/notes/commits:$commit_5) && sort -t" " -k2 >expect_list <<-EOF && - $(git rev-parse refs/notes/commits:$commit_2) $commit_2 - $(git rev-parse refs/notes/commits:$commit_3) $commit_3 - $(git rev-parse refs/notes/commits:$commit_5) $commit_5 + $note_2 $commit_2 + $note_3 $commit_3 + $note_5 $commit_5 EOF git notes list >actual && test_cmp expect_list actual @@ -721,7 +740,8 @@ test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' git notes show HEAD: >actual && test_cmp expect actual && echo "Note on a blob" >expect && - filename=$(git ls-tree --name-only HEAD | head -n1) && + git ls-tree --name-only HEAD >files && + filename=$(head -n1 files) && git notes add -m "Note on a blob" HEAD:$filename && git notes show HEAD:$filename >actual && test_cmp expect actual && @@ -745,10 +765,13 @@ test_expect_success 'create note from other note with "git notes add -C"' ' Notes: ${indent}order test EOF - git notes add -C $(git notes list HEAD^) && + note=$(git notes list HEAD^) && + git notes add -C $note && git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD^)" + git notes list HEAD^ >expect && + git notes list HEAD >actual && + test_cmp expect actual ' test_expect_success 'create note from non-existing note with "git notes add -C" fails' ' @@ -777,11 +800,12 @@ test_expect_success 'create note from blob with "git notes add -C" reuses blob i Notes: ${indent}This is a blob object EOF - blob=$(echo "This is a blob object" | git hash-object -w --stdin) && - git notes add -C $blob && + echo "This is a blob object" | git hash-object -w --stdin >blob && + git notes add -C $(cat blob) && git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$blob" + git notes list HEAD >actual && + test_cmp blob actual ' test_expect_success 'create note from other note with "git notes add -c"' ' @@ -797,7 +821,8 @@ test_expect_success 'create note from other note with "git notes add -c"' ' Notes: ${indent}yet another note EOF - MSG="yet another note" git notes add -c $(git notes list HEAD^^) && + note=$(git notes list HEAD^^) && + MSG="yet another note" git notes add -c $note && git log -1 >actual && test_cmp expect actual ' @@ -822,7 +847,8 @@ test_expect_success 'append to note from other note with "git notes append -C"' ${indent} ${indent}yet another note EOF - git notes append -C $(git notes list HEAD^) HEAD^ && + note=$(git notes list HEAD^) && + git notes append -C $note HEAD^ && git log -1 HEAD^ >actual && test_cmp expect actual ' @@ -839,7 +865,8 @@ test_expect_success 'create note from other note with "git notes append -c"' ' Notes: ${indent}other note EOF - MSG="other note" git notes append -c $(git notes list HEAD^) && + note=$(git notes list HEAD^) && + MSG="other note" git notes append -c $note && git log -1 >actual && test_cmp expect actual ' @@ -858,7 +885,8 @@ test_expect_success 'append to note from other note with "git notes append -c"' ${indent} ${indent}yet another note EOF - MSG="yet another note" git notes append -c $(git notes list HEAD) && + note=$(git notes list HEAD) && + MSG="yet another note" git notes append -c $note && git log -1 >actual && test_cmp expect actual ' @@ -878,7 +906,9 @@ test_expect_success 'copy note with "git notes copy"' ' git notes copy 8th 4th && git log 3rd..4th >actual && test_cmp expect actual && - test "$(git note list 4th)" = "$(git note list 8th)" + git notes list 4th >expect && + git notes list 8th >actual && + test_cmp expect actual ' test_expect_success 'copy note with "git notes copy" with default' ' @@ -899,14 +929,30 @@ test_expect_success 'copy note with "git notes copy" with default' ' git notes copy HEAD^ && git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD^)" + git notes list HEAD^ >expect && + git notes list HEAD >actual && + test_cmp expect actual ' test_expect_success 'prevent overwrite with "git notes copy"' ' test_must_fail git notes copy HEAD~2 HEAD && + cat >expect <<-EOF && + commit $commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:23:13 2005 -0700 + + ${indent}11th + + Notes: + ${indent}other note + ${indent} + ${indent}yet another note + EOF git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD^)" + git notes list HEAD^ >expect && + git notes list HEAD >actual && + test_cmp expect actual ' test_expect_success 'allow overwrite with "git notes copy -f"' ' @@ -924,7 +970,9 @@ test_expect_success 'allow overwrite with "git notes copy -f"' ' git notes copy -f HEAD~3 HEAD && git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD~3)" + git notes list HEAD~3 >expect && + git notes list HEAD >actual && + test_cmp expect actual ' test_expect_success 'allow overwrite with "git notes copy -f" with default' ' @@ -944,7 +992,9 @@ test_expect_success 'allow overwrite with "git notes copy -f" with default' ' git notes copy -f HEAD~2 && git log -1 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" + git notes list HEAD~2 >expect && + git notes list HEAD >actual && + test_cmp expect actual ' test_expect_success 'cannot copy note from object without notes' ' @@ -979,13 +1029,21 @@ test_expect_success 'git notes copy --stdin' ' ${indent} ${indent}yet another note EOF - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && - echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | - git notes copy --stdin && + from=$(git rev-parse HEAD~3) && + to=$(git rev-parse HEAD^) && + echo "$from" "$to" >copy && + from=$(git rev-parse HEAD~2) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >>copy && + git notes copy --stdin <copy && git log -2 >actual && test_cmp expect actual && - test "$(git notes list HEAD)" = "$(git notes list HEAD~2)" && - test "$(git notes list HEAD^)" = "$(git notes list HEAD~3)" + git notes list HEAD~2 >expect && + git notes list HEAD >actual && + test_cmp expect actual && + git notes list HEAD~3 >expect && + git notes list HEAD^ >actual && + test_cmp expect actual ' test_expect_success 'git notes copy --for-rewrite (unconfigured)' ' @@ -1006,9 +1064,13 @@ test_expect_success 'git notes copy --for-rewrite (unconfigured)' ' ${indent}14th EOF - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && - echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD~3) && + to=$(git rev-parse HEAD^) && + echo "$from" "$to" >copy && + from=$(git rev-parse HEAD~2) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >>copy && + git notes copy --for-rewrite=foo <copy && git log -2 >actual && test_cmp expect actual ' @@ -1041,17 +1103,23 @@ test_expect_success 'git notes copy --for-rewrite (enabled)' ' EOF test_config notes.rewriteMode overwrite && test_config notes.rewriteRef "refs/notes/*" && - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && - echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD~3) && + to=$(git rev-parse HEAD^) && + echo "$from" "$to" >copy && + from=$(git rev-parse HEAD~2) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >>copy && + git notes copy --for-rewrite=foo <copy && git log -2 >actual && test_cmp expect actual ' test_expect_success 'git notes copy --for-rewrite (disabled)' ' test_config notes.rewrite.bar false && - echo $(git rev-parse HEAD~3) $(git rev-parse HEAD) | - git notes copy --for-rewrite=bar && + from=$(git rev-parse HEAD~3) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + git notes copy --for-rewrite=bar <copy && git log -2 >actual && test_cmp expect actual ' @@ -1071,8 +1139,10 @@ test_expect_success 'git notes copy --for-rewrite (overwrite)' ' git notes add -f -m"a fresh note" HEAD^ && test_config notes.rewriteMode overwrite && test_config notes.rewriteRef "refs/notes/*" && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1080,8 +1150,10 @@ test_expect_success 'git notes copy --for-rewrite (overwrite)' ' test_expect_success 'git notes copy --for-rewrite (ignore)' ' test_config notes.rewriteMode ignore && test_config notes.rewriteRef "refs/notes/*" && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1103,8 +1175,10 @@ test_expect_success 'git notes copy --for-rewrite (append)' ' git notes add -f -m"another fresh note" HEAD^ && test_config notes.rewriteMode concatenate && test_config notes.rewriteRef "refs/notes/*" && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1131,9 +1205,13 @@ test_expect_success 'git notes copy --for-rewrite (append two to one)' ' git notes add -f -m"append 2" HEAD^^ && test_config notes.rewriteMode concatenate && test_config notes.rewriteRef "refs/notes/*" && - (echo $(git rev-parse HEAD^) $(git rev-parse HEAD) && - echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + from=$(git rev-parse HEAD^^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >>copy && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1142,8 +1220,10 @@ test_expect_success 'git notes copy --for-rewrite (append empty)' ' git notes remove HEAD^ && test_config notes.rewriteMode concatenate && test_config notes.rewriteRef "refs/notes/*" && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | - git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1163,8 +1243,10 @@ test_expect_success 'GIT_NOTES_REWRITE_MODE works' ' test_config notes.rewriteMode concatenate && test_config notes.rewriteRef "refs/notes/*" && git notes add -f -m"replacement note 1" HEAD^ && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | - GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo && + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && + GIT_NOTES_REWRITE_MODE=overwrite git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1184,9 +1266,11 @@ test_expect_success 'GIT_NOTES_REWRITE_REF works' ' git notes add -f -m"replacement note 2" HEAD^ && test_config notes.rewriteMode overwrite && test_unconfig notes.rewriteRef && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && GIT_NOTES_REWRITE_REF=refs/notes/commits:refs/notes/other \ - git notes copy --for-rewrite=foo && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && test_cmp expect actual ' @@ -1195,9 +1279,11 @@ test_expect_success 'GIT_NOTES_REWRITE_REF overrides config' ' git notes add -f -m"replacement note 3" HEAD^ && test_config notes.rewriteMode overwrite && test_config notes.rewriteRef refs/notes/other && - echo $(git rev-parse HEAD^) $(git rev-parse HEAD) | + from=$(git rev-parse HEAD^) && + to=$(git rev-parse HEAD) && + echo "$from" "$to" >copy && GIT_NOTES_REWRITE_REF=refs/notes/commits \ - git notes copy --for-rewrite=foo && + git notes copy --for-rewrite=foo <copy && git log -1 >actual && grep "replacement note 3" actual ' @@ -1212,26 +1298,36 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' ' test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' ' test_unconfig core.notesRef && sane_unset GIT_NOTES_REF && - test "$(git notes --ref=refs/heads/master get-ref)" = "refs/notes/refs/heads/master" + echo refs/notes/refs/heads/master >expect && + git notes --ref=refs/heads/master get-ref >actual && + test_cmp expect actual ' test_expect_success 'git notes get-ref (no overrides)' ' test_unconfig core.notesRef && sane_unset GIT_NOTES_REF && - test "$(git notes get-ref)" = "refs/notes/commits" + echo refs/notes/commits >expect && + git notes get-ref >actual && + test_cmp expect actual ' test_expect_success 'git notes get-ref (core.notesRef)' ' test_config core.notesRef refs/notes/foo && - test "$(git notes get-ref)" = "refs/notes/foo" + echo refs/notes/foo >expect && + git notes get-ref >actual && + test_cmp expect actual ' test_expect_success 'git notes get-ref (GIT_NOTES_REF)' ' - test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar" + echo refs/notes/bar >expect && + GIT_NOTES_REF=refs/notes/bar git notes get-ref >actual && + test_cmp expect actual ' test_expect_success 'git notes get-ref (--ref)' ' - test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz" + echo refs/notes/baz >expect && + GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref >actual && + test_cmp expect actual ' test_expect_success 'setup testing of empty notes' ' diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 1323f30fee..221b35f2df 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -64,7 +64,7 @@ test_expect_success 'rebase sets ORIG_HEAD to pre-rebase state' ' pre="$(git rev-parse --verify HEAD)" && git rebase master && test_cmp_rev "$pre" ORIG_HEAD && - ! test_cmp_rev "$pre" HEAD + test_cmp_rev ! "$pre" HEAD ' test_expect_success 'rebase, with <onto> and <upstream> specified as :/quuxery' ' diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 1f5122b632..ee8a8dba52 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -7,6 +7,8 @@ test_description='git rebase --merge --skip tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + # we assume the default git am -3 --skip strategy is tested independently # and always works :) @@ -20,6 +22,13 @@ test_expect_success setup ' git commit -a -m "hello world" && echo goodbye >> hello && git commit -a -m "goodbye" && + git tag goodbye && + + git checkout --detach && + git checkout HEAD^ . && + test_tick && + git commit -m reverted-goodbye && + git tag reverted-goodbye && git checkout -f skip-reference && echo moo > hello && @@ -76,4 +85,27 @@ test_expect_success 'moved back to branch correctly' ' test_debug 'gitk --all & sleep 1' +test_expect_success 'fixup that empties commit fails' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 fixup 2" git rebase -i \ + goodbye^ reverted-goodbye + ) +' + +test_expect_success 'squash that empties commit fails' ' + test_when_finished "git rebase --abort" && + ( + set_fake_editor && + test_must_fail env FAKE_LINES="1 squash 2" git rebase -i \ + goodbye^ reverted-goodbye + ) +' + +# Must be the last test in this file +test_expect_success '$EDITOR and friends are unchanged' ' + test_editor_unchanged +' + test_done diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index b847064f91..325072b0a3 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -61,7 +61,7 @@ test_run_rebase () { test_expect_$result "rebase $* -f rewrites even if upstream is an ancestor" " reset_rebase && git rebase $* -f b e && - ! test_cmp_rev e HEAD && + test_cmp_rev ! e HEAD && test_cmp_rev b HEAD~2 && test_linear_range 'd e' b.. " @@ -78,7 +78,7 @@ test_run_rebase () { test_expect_$result "rebase $* -f rewrites even if remote upstream is an ancestor" " reset_rebase && git rebase $* -f branch-b branch-e && - ! test_cmp_rev branch-e origin/branch-e && + test_cmp_rev ! branch-e origin/branch-e && test_cmp_rev branch-b HEAD~2 && test_linear_range 'd e' branch-b.. " @@ -368,7 +368,7 @@ test_run_rebase () { test_expect_$result "rebase $* -f --root on linear history causes re-write" " reset_rebase && git rebase $* -f --root c && - ! test_cmp_rev a HEAD~2 && + test_cmp_rev ! a HEAD~2 && test_linear_range 'a b c' HEAD " } diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh index 50e7960702..c8234062c6 100755 --- a/t/t3422-rebase-incompatible-options.sh +++ b/t/t3422-rebase-incompatible-options.sh @@ -61,8 +61,6 @@ test_rebase_am_only () { } test_rebase_am_only --whitespace=fix -test_rebase_am_only --ignore-whitespace -test_rebase_am_only --committer-date-is-author-date test_rebase_am_only -C4 test_expect_success REBASE_P '--preserve-merges incompatible with --signoff' ' diff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh index aaeac6eade..7024d49ae7 100755 --- a/t/t3429-rebase-edit-todo.sh +++ b/t/t3429-rebase-edit-todo.sh @@ -52,4 +52,34 @@ test_expect_success 'todo is re-read after reword and squash' ' test_cmp expected actual ' +test_expect_success 're-reading todo doesnt interfere with revert --edit' ' + git reset --hard third && + + git revert --edit third second && + + cat >expect <<-\EOF && + Revert "second" + Revert "third" + third + second + first + EOF + git log --format="%s" >actual && + test_cmp expect actual +' + +test_expect_success 're-reading todo doesnt interfere with cherry-pick --edit' ' + git reset --hard first && + + git cherry-pick --edit second third && + + cat >expect <<-\EOF && + third + second + first + EOF + git log --format="%s" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index a30d27e9f3..e72ca348ea 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -346,7 +346,7 @@ test_expect_success 'A root commit can be a cousin, treat it that way' ' git merge --allow-unrelated-histories khnum && test_tick && git rebase -f -r HEAD^ && - ! test_cmp_rev HEAD^2 khnum && + test_cmp_rev ! HEAD^2 khnum && test_cmp_graph HEAD^.. <<-\EOF && * Merge branch '\''khnum'\'' into asherah |\ @@ -468,4 +468,31 @@ test_expect_success '--rebase-merges with strategies' ' test_cmp expect G.t ' +test_expect_success '--rebase-merges with commit that can generate bad characters for filename' ' + git checkout -b colon-in-label E && + git merge -m "colon: this should work" G && + git rebase --rebase-merges --force-rebase E +' + +test_expect_success '--rebase-merges with message matched with onto label' ' + git checkout -b onto-label E && + git merge -m onto G && + git rebase --rebase-merges --force-rebase E && + test_cmp_graph <<-\EOF + * onto + |\ + | * G + | * F + * | E + |\ \ + | * | B + * | | D + | |/ + |/| + * | C + |/ + * A + EOF +' + test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 034ffc7e76..92f95b57da 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -64,7 +64,7 @@ test_rebase_same_head_ () { test_cmp_rev \$oldhead \$newhead elif test $cmp = diff then - ! test_cmp_rev \$oldhead \$newhead + test_cmp_rev ! \$oldhead \$newhead fi " } diff --git a/t/t3433-rebase-options-compatibility.sh b/t/t3433-rebase-options-compatibility.sh new file mode 100755 index 0000000000..5166f158dd --- /dev/null +++ b/t/t3433-rebase-options-compatibility.sh @@ -0,0 +1,131 @@ +#!/bin/sh +# +# Copyright (c) 2019 Rohit Ashiwal +# + +test_description='tests to ensure compatibility between am and interactive backends' + +. ./test-lib.sh + +GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30" +export GIT_AUTHOR_DATE + +# This is a special case in which both am and interactive backends +# provide the same output. It was done intentionally because +# both the backends fall short of optimal behaviour. +test_expect_success 'setup' ' + git checkout -b topic && + q_to_tab >file <<-\EOF && + line 1 + Qline 2 + line 3 + EOF + git add file && + git commit -m "add file" && + cat >file <<-\EOF && + line 1 + new line 2 + line 3 + EOF + git commit -am "update file" && + git tag side && + test_commit commit1 foo foo1 && + test_commit commit2 foo foo2 && + test_commit commit3 foo foo3 && + + git checkout --orphan master && + git rm --cached foo && + rm foo && + sed -e "s/^|//" >file <<-\EOF && + |line 1 + | line 2 + |line 3 + EOF + git add file && + git commit -m "add file" && + git tag main +' + +test_expect_success '--ignore-whitespace works with am backend' ' + cat >expect <<-\EOF && + line 1 + new line 2 + line 3 + EOF + test_must_fail git rebase main side && + git rebase --abort && + git rebase --ignore-whitespace main side && + test_cmp expect file +' + +test_expect_success '--ignore-whitespace works with interactive backend' ' + cat >expect <<-\EOF && + line 1 + new line 2 + line 3 + EOF + test_must_fail git rebase --merge main side && + git rebase --abort && + git rebase --merge --ignore-whitespace main side && + test_cmp expect file +' + +test_expect_success '--committer-date-is-author-date works with am backend' ' + git commit --amend && + git rebase --committer-date-is-author-date HEAD^ && + git show HEAD --pretty="format:%ai" >authortime && + git show HEAD --pretty="format:%ci" >committertime && + test_cmp authortime committertime +' + +test_expect_success '--committer-date-is-author-date works with interactive backend' ' + git commit --amend && + git rebase -i --committer-date-is-author-date HEAD^ && + git show HEAD --pretty="format:%ai" >authortime && + git show HEAD --pretty="format:%ci" >committertime && + test_cmp authortime committertime +' + +test_expect_success '--committer-date-is-author-date works with rebase -r' ' + git checkout side && + git merge --no-ff commit3 && + git rebase -r --root --committer-date-is-author-date && + git rev-list HEAD >rev_list && + while read HASH + do + git show $HASH --pretty="format:%ai" >authortime + git show $HASH --pretty="format:%ci" >committertime + test_cmp authortime committertime + done <rev_list +' + +# Checking for +0000 in author time is enough since default +# timezone is UTC, but the timezone used while committing +# sets to +0530. +test_expect_success '--ignore-date works with am backend' ' + git commit --amend --date="$GIT_AUTHOR_DATE" && + git rebase --ignore-date HEAD^ && + git show HEAD --pretty="format:%ai" >authortime && + grep "+0000" authortime +' + +test_expect_success '--ignore-date works with interactive backend' ' + git commit --amend --date="$GIT_AUTHOR_DATE" && + git rebase --ignore-date -i HEAD^ && + git show HEAD --pretty="format:%ai" >authortime && + grep "+0000" authortime +' + +test_expect_success '--ignore-date works with rebase -r' ' + git checkout side && + git merge --no-ff commit3 && + git rebase -r --root --ignore-date && + git rev-list HEAD >rev_list && + while read HASH + do + git show $HASH --pretty="format:%ai" >authortime + grep "+0000" authortime + done <rev_list +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index d1c68af8c5..1c51a9131d 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -106,7 +106,7 @@ test_expect_success 'cherry-pick on unborn branch' ' rm -rf * && git cherry-pick initial && git diff --quiet initial && - ! test_cmp_rev initial HEAD + test_cmp_rev ! initial HEAD ' test_expect_success 'cherry-pick "-" to pick from previous branch' ' diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh index b457333e18..23070a7b73 100755 --- a/t/t3508-cherry-pick-many-commits.sh +++ b/t/t3508-cherry-pick-many-commits.sh @@ -5,7 +5,7 @@ test_description='test cherry-picking many commits' . ./test-lib.sh check_head_differs_from() { - ! test_cmp_rev HEAD "$1" + test_cmp_rev ! HEAD "$1" } check_head_equals() { diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 8c8cca5bfb..0ea858d652 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -113,9 +113,10 @@ test_expect_success '"rm" command printed' ' echo frotz >test-file && git add test-file && git commit -m "add file for rm test" && - git rm test-file >rm-output && - test $(grep "^rm " rm-output | wc -l) = 1 && - rm -f test-file rm-output && + git rm test-file >rm-output.raw && + grep "^rm " rm-output.raw >rm-output && + test_line_count = 1 rm-output && + rm -f test-file rm-output.raw rm-output && git commit -m "remove file from rm test" ' @@ -250,6 +251,7 @@ test_expect_success 'choking "git rm" should not let it die with cruft' ' echo "100644 $hash 0 some-file-$i" i=$(( $i + 1 )) done | git update-index --index-info && + # git command is intentionally placed upstream of pipe to induce SIGPIPE git rm -n "some-file-*" | : && test_path_is_missing .git/index.lock ' @@ -303,7 +305,8 @@ EOF test_expect_success 'rm removes empty submodules from work tree' ' mkdir submod && - git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) submod && + hash=$(git rev-parse HEAD) && + git update-index --add --cacheinfo 160000 "$hash" submod && git config -f .gitmodules submodule.sub.url ./. && git config -f .gitmodules submodule.sub.path submod && git submodule init && @@ -622,7 +625,8 @@ test_expect_success 'setup subsubmodule' ' git submodule update && ( cd submod && - git update-index --add --cacheinfo 160000 $(git rev-parse HEAD) subsubmod && + hash=$(git rev-parse HEAD) && + git update-index --add --cacheinfo 160000 "$hash" subsubmod && git config -f .gitmodules submodule.sub.url ../. && git config -f .gitmodules submodule.sub.path subsubmod && git submodule init && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index d50e165ca8..d4f9386621 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -647,4 +647,29 @@ test_expect_success 'checkout -p works with pathological context lines' ' test_write_lines a b a b a a b a b a >expect && test_cmp expect a ' + +test_expect_success 'show help from add--helper' ' + git reset --hard && + cat >expect <<-EOF && + + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>> <BOLD;RED>status - show paths with changes<RESET> + <BOLD;RED>update - add working tree state to the staged set of changes<RESET> + <BOLD;RED>revert - revert staged set of changes back to the HEAD version<RESET> + <BOLD;RED>patch - pick hunks and update selectively<RESET> + <BOLD;RED>diff - view diff between HEAD and index<RESET> + <BOLD;RED>add untracked - add contents of untracked files to the staged set of changes<RESET> + <BOLD>*** Commands ***<RESET> + 1: <BOLD;BLUE>s<RESET>tatus 2: <BOLD;BLUE>u<RESET>pdate 3: <BOLD;BLUE>r<RESET>evert 4: <BOLD;BLUE>a<RESET>dd untracked + 5: <BOLD;BLUE>p<RESET>atch 6: <BOLD;BLUE>d<RESET>iff 7: <BOLD;BLUE>q<RESET>uit 8: <BOLD;BLUE>h<RESET>elp + <BOLD;BLUE>What now<RESET>>$SP + Bye. + EOF + test_write_lines h | GIT_PAGER_IN_USE=true TERM=vt100 git add -i >actual.colored && + test_decode_color <actual.colored >actual && + test_i18ncmp expect actual +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index eadaf57262..4c540b1d70 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -16,7 +16,8 @@ test_expect_success "Ray Lehtiniemi's example" ' } while (0); EOF git update-index --add x && - before=$(git rev-parse --short $(git hash-object x)) && + old_hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$old_hash_x") && cat <<-\EOF >x && do @@ -25,7 +26,8 @@ test_expect_success "Ray Lehtiniemi's example" ' } while (0); EOF - after=$(git rev-parse --short $(git hash-object x)) && + new_hash_x=$(git hash-object x) && + after=$(git rev-parse --short "$new_hash_x") && cat <<-EOF >expect && diff --git a/x b/x @@ -63,7 +65,8 @@ test_expect_success 'another test, without options' ' EOF git update-index x && - before=$(git rev-parse --short $(git hash-object x)) && + old_hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$old_hash_x") && tr "_" " " <<-\EOF >x && _ whitespace at beginning @@ -73,7 +76,8 @@ test_expect_success 'another test, without options' ' unchanged line CR at end EOF - after=$(git rev-parse --short $(git hash-object x)) && + new_hash_x=$(git hash-object x) && + after=$(git rev-parse --short "$new_hash_x") && tr "Q_" "\015 " <<-EOF >expect && diff --git a/x b/x @@ -526,13 +530,15 @@ test_expect_success 'ignore-blank-lines: mix changes and blank lines' ' test_expect_success 'check mixed spaces and tabs in indent' ' # This is indented with SP HT SP. echo " foo();" >x && - git diff --check | grep "space before tab in indent" + test_must_fail git diff --check >check && + grep "space before tab in indent" check ' test_expect_success 'check mixed tabs and spaces in indent' ' # This is indented with HT SP HT. echo " foo();" >x && - git diff --check | grep "space before tab in indent" + test_must_fail git diff --check >check && + grep "space before tab in indent" check ' test_expect_success 'check with no whitespace errors' ' @@ -753,20 +759,23 @@ test_expect_success 'check tab-in-indent excluded from wildcard whitespace attri test_expect_success 'line numbers in --check output are correct' ' echo "" >x && echo "foo(); " >>x && - git diff --check | grep "x:2:" + test_must_fail git diff --check >check && + grep "x:2:" check ' test_expect_success 'checkdiff detects new trailing blank lines (1)' ' echo "foo();" >x && echo "" >>x && - git diff --check | grep "new blank line" + test_must_fail git diff --check >check && + grep "new blank line" check ' test_expect_success 'checkdiff detects new trailing blank lines (2)' ' - { echo a; echo b; echo; echo; } >x && + test_write_lines a b "" "" >x && git add x && - { echo a; echo; echo; echo; echo; } >x && - git diff --check | grep "new blank line" + test_write_lines a "" "" "" "" >x && + test_must_fail git diff --check >check && + grep "new blank line" check ' test_expect_success 'checkdiff allows new blank lines' ' @@ -794,14 +803,16 @@ test_expect_success 'whitespace-only changes reported across renames' ' git reset --hard && for i in 1 2 3 4 5 6 7 8 9; do echo "$i$i$i$i$i$i"; done >x && git add x && - before=$(git rev-parse --short $(git hash-object x)) && + hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$hash_x") && git commit -m "base" && sed -e "5s/^/ /" x >z && git rm x && git add z && - after=$(git rev-parse --short $(git hash-object z)) && - git diff -w -M --cached | - sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" >actual && + hash_z=$(git hash-object z) && + after=$(git rev-parse --short "$hash_z") && + git diff -w -M --cached >actual.raw && + sed -e "/^similarity index /s/[0-9][0-9]*/NUM/" actual.raw >actual && cat <<-EOF >expect && diff --git a/x b/z similarity index NUM% @@ -840,7 +851,8 @@ test_expect_success 'combined diff with autocrlf conversion' ' git config core.autocrlf true && test_must_fail git merge master && - git diff | sed -e "1,/^@@@/d" >actual && + git diff >actual.raw && + sed -e "1,/^@@@/d" actual.raw >actual && ! grep "^-" actual ' @@ -864,11 +876,14 @@ test_expect_success 'diff that introduces a line with only tabs' ' git config core.whitespace blank-at-eol && git reset --hard && echo "test" >x && - before=$(git rev-parse --short $(git hash-object x)) && + old_hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$old_hash_x") && git commit -m "initial" x && echo "{NTN}" | tr "NT" "\n\t" >>x && - after=$(git rev-parse --short $(git hash-object x)) && - git diff --color | test_decode_color >current && + new_hash_x=$(git hash-object x) && + after=$(git rev-parse --short "$new_hash_x") && + git diff --color >current.raw && + test_decode_color <current.raw >current && cat >expected <<-EOF && <BOLD>diff --git a/x b/x<RESET> @@ -891,17 +906,19 @@ test_expect_success 'diff that introduces and removes ws breakages' ' echo "0. blank-at-eol " && echo "1. blank-at-eol " } >x && - before=$(git rev-parse --short $(git hash-object x)) && + old_hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$old_hash_x") && git commit -a --allow-empty -m preimage && { echo "0. blank-at-eol " && echo "1. still-blank-at-eol " && echo "2. and a new line " } >x && - after=$(git rev-parse --short $(git hash-object x)) && + new_hash_x=$(git hash-object x) && + after=$(git rev-parse --short "$new_hash_x") && - git diff --color | - test_decode_color >current && + git diff --color >current.raw && + test_decode_color <current.raw >current && cat >expected <<-EOF && <BOLD>diff --git a/x b/x<RESET> @@ -925,14 +942,16 @@ test_expect_success 'ws-error-highlight test setup' ' echo "0. blank-at-eol " && echo "1. blank-at-eol " } >x && - before=$(git rev-parse --short $(git hash-object x)) && + old_hash_x=$(git hash-object x) && + before=$(git rev-parse --short "$old_hash_x") && git commit -a --allow-empty -m preimage && { echo "0. blank-at-eol " && echo "1. still-blank-at-eol " && echo "2. and a new line " } >x && - after=$(git rev-parse --short $(git hash-object x)) && + new_hash_x=$(git hash-object x) && + after=$(git rev-parse --short "$new_hash_x") && cat >expect.default-old <<-EOF && <BOLD>diff --git a/x b/x<RESET> @@ -974,32 +993,32 @@ test_expect_success 'ws-error-highlight test setup' ' test_expect_success 'test --ws-error-highlight option' ' - git diff --color --ws-error-highlight=default,old | - test_decode_color >current && + git diff --color --ws-error-highlight=default,old >current.raw && + test_decode_color <current.raw >current && test_cmp expect.default-old current && - git diff --color --ws-error-highlight=all | - test_decode_color >current && + git diff --color --ws-error-highlight=all >current.raw && + test_decode_color <current.raw >current && test_cmp expect.all current && - git diff --color --ws-error-highlight=none | - test_decode_color >current && + git diff --color --ws-error-highlight=none >current.raw && + test_decode_color <current.raw >current && test_cmp expect.none current ' test_expect_success 'test diff.wsErrorHighlight config' ' - git -c diff.wsErrorHighlight=default,old diff --color | - test_decode_color >current && + git -c diff.wsErrorHighlight=default,old diff --color >current.raw && + test_decode_color <current.raw >current && test_cmp expect.default-old current && - git -c diff.wsErrorHighlight=all diff --color | - test_decode_color >current && + git -c diff.wsErrorHighlight=all diff --color >current.raw && + test_decode_color <current.raw >current && test_cmp expect.all current && - git -c diff.wsErrorHighlight=none diff --color | - test_decode_color >current && + git -c diff.wsErrorHighlight=none diff --color >current.raw && + test_decode_color <current.raw >current && test_cmp expect.none current ' @@ -1007,18 +1026,18 @@ test_expect_success 'test diff.wsErrorHighlight config' ' test_expect_success 'option overrides diff.wsErrorHighlight' ' git -c diff.wsErrorHighlight=none \ - diff --color --ws-error-highlight=default,old | - test_decode_color >current && + diff --color --ws-error-highlight=default,old >current.raw && + test_decode_color <current.raw >current && test_cmp expect.default-old current && git -c diff.wsErrorHighlight=default \ - diff --color --ws-error-highlight=all | - test_decode_color >current && + diff --color --ws-error-highlight=all >current.raw && + test_decode_color <current.raw >current && test_cmp expect.all current && git -c diff.wsErrorHighlight=all \ - diff --color --ws-error-highlight=none | - test_decode_color >current && + diff --color --ws-error-highlight=none >current.raw && + test_decode_color <current.raw >current && test_cmp expect.none current ' @@ -1038,7 +1057,8 @@ test_expect_success 'detect moved code, complete file' ' git mv test.c main.c && test_config color.diff.oldMoved "normal red" && test_config color.diff.newMoved "normal green" && - git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual && + git diff HEAD --color-moved=zebra --color --no-renames >actual.raw && + test_decode_color <actual.raw >actual && cat >expected <<-EOF && <BOLD>diff --git a/main.c b/main.c<RESET> <BOLD>new file mode 100644<RESET> @@ -1141,9 +1161,12 @@ test_expect_success 'detect malicious moved code, inside file' ' bar(); } EOF - after_main=$(git rev-parse --short $(git hash-object main.c)) && - after_test=$(git rev-parse --short $(git hash-object test.c)) && - git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual && + hash_main=$(git hash-object main.c) && + after_main=$(git rev-parse --short "$hash_main") && + hash_test=$(git hash-object test.c) && + after_test=$(git rev-parse --short "$hash_test") && + git diff HEAD --no-renames --color-moved=zebra --color >actual.raw && + test_decode_color <actual.raw >actual && cat <<-EOF >expected && <BOLD>diff --git a/main.c b/main.c<RESET> <BOLD>index $before_main..$after_main 100644<RESET> @@ -1192,7 +1215,8 @@ test_expect_success 'plain moved code, inside file' ' test_config color.diff.oldMovedAlternative "blue" && test_config color.diff.newMovedAlternative "yellow" && # needs previous test as setup - git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual && + git diff HEAD --no-renames --color-moved=plain --color >actual.raw && + test_decode_color <actual.raw >actual && cat <<-EOF >expected && <BOLD>diff --git a/main.c b/main.c<RESET> <BOLD>index $before_main..$after_main 100644<RESET> @@ -1771,7 +1795,8 @@ test_expect_success 'move detection with submodules' ' ! grep BRED decoded_actual && # nor did we mess with it another way - git diff --submodule=diff --color | test_decode_color >expect && + git diff --submodule=diff --color >expect.raw && + test_decode_color <expect.raw >expect && test_cmp expect decoded_actual && rm -rf bananas && git submodule deinit bananas diff --git a/t/t4018/python-async-def b/t/t4018/python-async-def new file mode 100644 index 0000000000..87640e03d2 --- /dev/null +++ b/t/t4018/python-async-def @@ -0,0 +1,4 @@ +async def RIGHT(pi: int = 3.14): + while True: + break + return ChangeMe() diff --git a/t/t4018/python-class b/t/t4018/python-class new file mode 100644 index 0000000000..ba9e741430 --- /dev/null +++ b/t/t4018/python-class @@ -0,0 +1,4 @@ +class RIGHT(int, str): + # comment + # another comment + # ChangeMe diff --git a/t/t4018/python-def b/t/t4018/python-def new file mode 100644 index 0000000000..e50b31b0ad --- /dev/null +++ b/t/t4018/python-def @@ -0,0 +1,4 @@ +def RIGHT(pi: int = 3.14): + while True: + break + return ChangeMe() diff --git a/t/t4018/python-indented-async-def b/t/t4018/python-indented-async-def new file mode 100644 index 0000000000..f5d03258af --- /dev/null +++ b/t/t4018/python-indented-async-def @@ -0,0 +1,7 @@ +class Foo: + async def RIGHT(self, x: int): + return [ + 1, + 2, + ChangeMe, + ] diff --git a/t/t4018/python-indented-class b/t/t4018/python-indented-class new file mode 100644 index 0000000000..19b4f35c4c --- /dev/null +++ b/t/t4018/python-indented-class @@ -0,0 +1,5 @@ +if TYPE_CHECKING: + class RIGHT: + # comment + # another comment + # ChangeMe diff --git a/t/t4018/python-indented-def b/t/t4018/python-indented-def new file mode 100644 index 0000000000..208fbadd2b --- /dev/null +++ b/t/t4018/python-indented-def @@ -0,0 +1,7 @@ +class Foo: + def RIGHT(self, x: int): + return [ + 1, + 2, + ChangeMe, + ] diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index b5a56895e8..94680836ce 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -354,7 +354,7 @@ test_expect_failure 'combine diff coalesce three parents' ' ' # Test for a bug reported at -# https://public-inbox.org/git/20130515143508.GO25742@login.drsnuggles.stderr.nl/ +# https://lore.kernel.org/git/20130515143508.GO25742@login.drsnuggles.stderr.nl/ # where a delete lines were missing from combined diff output when they # occurred exactly before the context lines of a later change. test_expect_success 'combine diff missing delete bug' ' diff --git a/t/t4138-apply-ws-expansion.sh b/t/t4138-apply-ws-expansion.sh index 3b636a63a3..b19faeb67a 100755 --- a/t/t4138-apply-ws-expansion.sh +++ b/t/t4138-apply-ws-expansion.sh @@ -17,8 +17,8 @@ test_expect_success setup ' printf "\t%s\n" 1 2 3 >after && printf "%64s\n" a b c >>after && printf "\t%s\n" 4 5 6 >>after && - git diff --no-index before after | - sed -e "s/before/test-1/" -e "s/after/test-1/" >patch1.patch && + test_expect_code 1 git diff --no-index before after >patch1.patch.raw && + sed -e "s/before/test-1/" -e "s/after/test-1/" patch1.patch.raw >patch1.patch && printf "%64s\n" 1 2 3 4 5 6 >test-1 && printf "%64s\n" 1 2 3 a b c 4 5 6 >expect-1 && @@ -33,8 +33,8 @@ test_expect_success setup ' x=$(( $x + 1 )) done && printf "\t%s\n" d e f >>after && - git diff --no-index before after | - sed -e "s/before/test-2/" -e "s/after/test-2/" >patch2.patch && + test_expect_code 1 git diff --no-index before after >patch2.patch.raw && + sed -e "s/before/test-2/" -e "s/after/test-2/" patch2.patch.raw >patch2.patch && printf "%64s\n" a b c d e f >test-2 && printf "%64s\n" a b c >expect-2 && x=1 && @@ -56,8 +56,8 @@ test_expect_success setup ' x=$(( $x + 1 )) done && printf "\t%s\n" d e f >>after && - git diff --no-index before after | - sed -e "s/before/test-3/" -e "s/after/test-3/" >patch3.patch && + test_expect_code 1 git diff --no-index before after >patch3.patch.raw && + sed -e "s/before/test-3/" -e "s/after/test-3/" patch3.patch.raw >patch3.patch && printf "%64s\n" a b c d e f >test-3 && printf "%64s\n" a b c >expect-3 && x=0 && @@ -84,8 +84,8 @@ test_expect_success setup ' printf "\t%02d\n" $x >>after x=$(( $x + 1 )) done && - git diff --no-index before after | - sed -e "s/before/test-4/" -e "s/after/test-4/" >patch4.patch && + test_expect_code 1 git diff --no-index before after >patch4.patch.raw && + sed -e "s/before/test-4/" -e "s/after/test-4/" patch4.patch.raw >patch4.patch && >test-4 && x=0 && while test $x -lt 50 diff --git a/t/t4202-log.sh b/t/t4202-log.sh index ab0d021365..2c9489484a 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1570,6 +1570,14 @@ test_expect_success GPG 'setup signed branch' ' git commit -S -m signed_commit ' +test_expect_success GPG 'setup signed branch with subkey' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b signed-subkey master && + echo foo >foo && + git add foo && + git commit -SB7227189 -m signed_commit +' + test_expect_success GPGSM 'setup signed branch x509' ' test_when_finished "git reset --hard && git checkout master" && git checkout -b signed-x509 master && @@ -1580,6 +1588,18 @@ test_expect_success GPGSM 'setup signed branch x509' ' git commit -S -m signed_commit ' +test_expect_success GPGSM 'log x509 fingerprint' ' + echo "F8BF62E0693D0694816377099909C779FA23FD65 | " >expect && + git log -n1 --format="%GF | %GP" signed-x509 >actual && + test_cmp expect actual +' + +test_expect_success GPGSM 'log OpenPGP fingerprint' ' + echo "D4BE22311AD3131E5EDA29A461092E85B7227189" > expect && + git log -n1 --format="%GP" signed-subkey >actual && + test_cmp expect actual +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual && diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 2f251b27d0..204c149d5a 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -134,6 +134,36 @@ test_expect_failure C_LOCALE_OUTPUT 'NUL termination with --stat' ' test_cmp expected actual ' +for p in short medium full fuller email raw +do + test_expect_success "NUL termination with --reflog --pretty=$p" ' + revs="$(git rev-list --reflog)" && + for r in $revs + do + git show -s "$r" --pretty="$p" && + printf "\0" || return 1 + done >expect && + { + git log -z --reflog --pretty="$p" && + printf "\0" + } >actual && + test_cmp expect actual + ' +done + +test_expect_success 'NUL termination with --reflog --pretty=oneline' ' + revs="$(git rev-list --reflog)" && + for r in $revs + do + git show -s --pretty=oneline "$r" >raw && + cat raw | lf_to_nul || exit 1 + done >expect && + # the trailing NUL is already produced so we do not need to + # output another one + git log -z --pretty=oneline --reflog >actual && + test_cmp expect actual +' + test_expect_success 'setup more commits' ' test_commit "message one" one one message-one && test_commit "message two" two two message-two && @@ -503,6 +533,12 @@ test_expect_success 'ISO and ISO-strict date formats display the same values' ' test_cmp expected actual ' +test_expect_success 'short date' ' + git log --format=%ad%n%cd --date=short >expected && + git log --format=%as%n%cs >actual && + test_cmp expected actual +' + # get new digests (with no abbreviations) test_expect_success 'set up log decoration tests' ' head1=$(git rev-parse --verify HEAD~0) && @@ -788,4 +824,47 @@ test_expect_success '%S in git log --format works with other placeholders (part test_cmp expect actual ' +test_expect_success 'log --pretty=reference' ' + git log --pretty="tformat:%h (%s, %as)" >expect && + git log --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference with log.date is overridden by short date' ' + git log --pretty="tformat:%h (%s, %as)" >expect && + test_config log.date rfc && + git log --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference with explicit date overrides short date' ' + git log --date=rfc --pretty="tformat:%h (%s, %ad)" >expect && + git log --date=rfc --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference is never unabbreviated' ' + git log --pretty="tformat:%h (%s, %as)" >expect && + git log --no-abbrev-commit --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference is never decorated' ' + git log --pretty="tformat:%h (%s, %as)" >expect && + git log --decorate=short --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference does not output reflog info' ' + git log --walk-reflogs --pretty="tformat:%h (%s, %as)" >expect && + git log --walk-reflogs --pretty=reference >actual && + test_cmp expect actual +' + +test_expect_success 'log --pretty=reference is colored appropriately' ' + git log --color=always --pretty="tformat:%C(auto)%h (%s, %as)" >expect && + git log --color=always --pretty=reference >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index 6e61f57f09..c3792081e6 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -70,7 +70,7 @@ do then force_regex=.* fi - test_expect_success !MINGW,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not find non-reencoded values (latin1 + locale)" " + test_expect_success !MINGW,!REGEX_ILLSEQ,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not find non-reencoded values (latin1 + locale)" " cat >expect <<-\EOF && latin1 utf8 @@ -84,7 +84,7 @@ do test_must_be_empty actual " - test_expect_success !MINGW,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not die on invalid UTF-8 value (latin1 + locale + invalid needle)" " + test_expect_success !MINGW,!REGEX_ILLSEQ,GETTEXT_LOCALE,$prereq "-c grep.patternType=$engine log --grep does not die on invalid UTF-8 value (latin1 + locale + invalid needle)" " LC_ALL=\"$is_IS_locale\" git -c grep.patternType=$engine log --encoding=ISO-8859-1 --format=%s --grep=\"$force_regex$invalid_e\" >actual && test_must_be_empty actual " diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh index e525466de0..0aec8619e2 100755 --- a/t/t5314-pack-cycle-detection.sh +++ b/t/t5314-pack-cycle-detection.sh @@ -53,7 +53,7 @@ immediately after the lookup for "dummy". -# Create a pack containing the the tree $1 and blob $1:file, with +# Create a pack containing the tree $1 and blob $1:file, with # the latter stored as a delta against $2:file. # # We convince pack-objects to make the delta in the direction of our choosing diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 2d2f5d0229..dc0446574b 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -45,12 +45,7 @@ test_expect_success 'verify blob:none packfile has no blobs' ' git -C r1 index-pack ../filter.pack && git -C r1 verify-pack -v ../filter.pack >verify_result && - grep blob verify_result | - awk -f print_1.awk | - sort >observed && - - nr=$(wc -l <observed) && - test 0 -eq $nr + ! grep blob verify_result ' test_expect_success 'verify normal and blob:none packfiles have same commits/trees' ' @@ -72,7 +67,8 @@ test_expect_success 'get an error for missing tree object' ' echo foo >r5/foo && git -C r5 add foo && git -C r5 commit -m "foo" && - del=$(git -C r5 rev-parse HEAD^{tree} | sed "s|..|&/|") && + git -C r5 rev-parse HEAD^{tree} >tree && + del=$(sed "s|..|&/|" tree) && rm r5/.git/objects/$del && test_must_fail git -C r5 pack-objects --revs --stdout 2>bad_tree <<-EOF && HEAD @@ -148,12 +144,7 @@ test_expect_success 'verify blob:limit=500 omits all blobs' ' git -C r2 index-pack ../filter.pack && git -C r2 verify-pack -v ../filter.pack >verify_result && - grep blob verify_result | - awk -f print_1.awk | - sort >observed && - - nr=$(wc -l <observed) && - test 0 -eq $nr + ! grep blob verify_result ' test_expect_success 'verify blob:limit=1000' ' @@ -163,12 +154,7 @@ test_expect_success 'verify blob:limit=1000' ' git -C r2 index-pack ../filter.pack && git -C r2 verify-pack -v ../filter.pack >verify_result && - grep blob verify_result | - awk -f print_1.awk | - sort >observed && - - nr=$(wc -l <observed) && - test 0 -eq $nr + ! grep blob verify_result ' test_expect_success 'verify blob:limit=1001' ' @@ -230,10 +216,9 @@ test_expect_success 'verify explicitly specifying oversized blob in input' ' awk -f print_2.awk ls_files_result | sort >expected && - git -C r2 pack-objects --revs --stdout --filter=blob:limit=1k >filter.pack <<-EOF && - HEAD - $(git -C r2 rev-parse HEAD:large.10000) - EOF + echo HEAD >objects && + git -C r2 rev-parse HEAD:large.10000 >>objects && + git -C r2 pack-objects --revs --stdout --filter=blob:limit=1k <objects >filter.pack && git -C r2 index-pack ../filter.pack && git -C r2 verify-pack -v ../filter.pack >verify_result && @@ -377,7 +362,8 @@ test_expect_success 'verify sparse:oid=OID' ' awk -f print_2.awk ls_files_result | sort >expected && - oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) && + git -C r4 ls-files -s pattern >staged && + oid=$(awk -f print_2.awk staged) && git -C r4 pack-objects --revs --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF && HEAD EOF diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 5a3e4e331a..3f03de6018 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -132,7 +132,7 @@ test_expect_success 'commit-graph write progress off for redirected stderr' ' test_expect_success 'commit-graph write force progress on for stderr' ' cd "$TRASH_DIRECTORY/full" && - git commit-graph write --progress 2>err && + GIT_PROGRESS_DELAY=0 git commit-graph write --progress 2>err && test_file_not_empty err ' @@ -150,7 +150,7 @@ test_expect_success 'commit-graph verify progress off for redirected stderr' ' test_expect_success 'commit-graph verify force progress on for stderr' ' cd "$TRASH_DIRECTORY/full" && - git commit-graph verify --progress 2>err && + GIT_PROGRESS_DELAY=0 git commit-graph verify --progress 2>err && test_file_not_empty err ' diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 43e1d8d4d2..d7b9f9078f 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -267,7 +267,7 @@ test_expect_success 'ls-remote --symref omits filtered-out matches' ' ' test_lazy_prereq GIT_DAEMON ' - git env--helper --type=bool --default=true --exit-code GIT_TEST_GIT_DAEMON + test_bool_env GIT_TEST_GIT_DAEMON true ' # This test spawns a daemon, so run it only if the user would be OK with diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index cf4cc32fd0..602d996a33 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -5,7 +5,7 @@ test_description='pulling into void' . ./test-lib.sh modify () { - sed -e "$1" <"$2" >"$2.x" && + sed -e "$1" "$2" >"$2.x" && mv "$2.x" "$2" } @@ -15,8 +15,10 @@ test_pull_autostash () { git add new_file && git pull "$@" . copy && test_cmp_rev HEAD^ copy && - test "$(cat new_file)" = dirty && - test "$(cat file)" = "modified again" + echo dirty >expect && + test_cmp expect new_file && + echo "modified again" >expect && + test_cmp expect file } test_pull_autostash_fail () { @@ -39,8 +41,8 @@ test_expect_success 'pulling into void' ' cd cloned && git pull .. ) && - test -f file && - test -f cloned/file && + test_path_is_file file && + test_path_is_file cloned/file && test_cmp file cloned/file ' @@ -50,8 +52,8 @@ test_expect_success 'pulling into void using master:master' ' cd cloned-uho && git pull .. master:master ) && - test -f file && - test -f cloned-uho/file && + test_path_is_file file && + test_path_is_file cloned-uho/file && test_cmp file cloned-uho/file ' @@ -99,7 +101,7 @@ test_expect_success 'pulling into void must not create an octopus' ' ( cd cloned-octopus && test_must_fail git pull .. master master && - ! test -f file + test_path_is_missing file ) ' @@ -110,9 +112,11 @@ test_expect_success 'test . as a remote' ' echo updated >file && git commit -a -m updated && git checkout copy && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && git pull && - test "$(cat file)" = updated && + echo updated >expect && + test_cmp expect file && git reflog -1 >reflog.actual && sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && echo "OBJID HEAD@{0}: pull: Fast-forward" >reflog.expected && @@ -125,9 +129,11 @@ test_expect_success 'the default remote . should not break explicit pull' ' git commit -a -m modified && git checkout copy && git reset --hard HEAD^ && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && git pull . second && - test "$(cat file)" = modified && + echo modified >expect && + test_cmp expect file && git reflog -1 >reflog.actual && sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && echo "OBJID HEAD@{0}: pull . second: Fast-forward" >reflog.expected && @@ -137,10 +143,11 @@ test_expect_success 'the default remote . should not break explicit pull' ' test_expect_success 'fail if wildcard spec does not match any refs' ' git checkout -b test copy^ && test_when_finished "git checkout -f copy && git branch -D test" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_must_fail git pull . "refs/nonexisting1/*:refs/nonexisting2/*" 2>err && test_i18ngrep "no candidates for merging" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'fail if no branches specified with non-default remote' ' @@ -148,11 +155,12 @@ test_expect_success 'fail if no branches specified with non-default remote' ' test_when_finished "git remote remove test_remote" && git checkout -b test copy^ && test_when_finished "git checkout -f copy && git branch -D test" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_config branch.test.remote origin && test_must_fail git pull test_remote 2>err && test_i18ngrep "specify a branch on the command line" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'fail if not on a branch' ' @@ -160,10 +168,11 @@ test_expect_success 'fail if not on a branch' ' test_when_finished "git remote remove origin" && git checkout HEAD^ && test_when_finished "git checkout -f copy" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_must_fail git pull 2>err && test_i18ngrep "not currently on a branch" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'fail if no configuration for current branch' ' @@ -172,10 +181,11 @@ test_expect_success 'fail if no configuration for current branch' ' git checkout -b test copy^ && test_when_finished "git checkout -f copy && git branch -D test" && test_config branch.test.remote test_remote && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_must_fail git pull 2>err && test_i18ngrep "no tracking information" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'pull --all: fail if no configuration for current branch' ' @@ -184,10 +194,11 @@ test_expect_success 'pull --all: fail if no configuration for current branch' ' git checkout -b test copy^ && test_when_finished "git checkout -f copy && git branch -D test" && test_config branch.test.remote test_remote && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_must_fail git pull --all 2>err && test_i18ngrep "There is no tracking information" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'fail if upstream branch does not exist' ' @@ -195,26 +206,31 @@ test_expect_success 'fail if upstream branch does not exist' ' test_when_finished "git checkout -f copy && git branch -D test" && test_config branch.test.remote . && test_config branch.test.merge refs/heads/nonexisting && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_must_fail git pull 2>err && test_i18ngrep "no such ref was fetched" err && - test "$(cat file)" = file + test_cmp expect file ' test_expect_success 'fail if the index has unresolved entries' ' git checkout -b third second^ && test_when_finished "git checkout -f copy && git branch -D third" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && test_commit modified2 file && - test -z "$(git ls-files -u)" && + git ls-files -u >unmerged && + test_must_be_empty unmerged && test_must_fail git pull . second && - test -n "$(git ls-files -u)" && + git ls-files -u >unmerged && + test_file_not_empty unmerged && cp file expected && test_must_fail git pull . second 2>err && test_i18ngrep "Pulling is not possible because you have unmerged files." err && test_cmp expected file && git add file && - test -z "$(git ls-files -u)" && + git ls-files -u >unmerged && + test_must_be_empty unmerged && test_must_fail git pull . second 2>err && test_i18ngrep "You have not concluded your merge" err && test_cmp expected file @@ -223,36 +239,42 @@ test_expect_success 'fail if the index has unresolved entries' ' test_expect_success 'fast-forwards working tree if branch head is updated' ' git checkout -b third second^ && test_when_finished "git checkout -f copy && git branch -D third" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && git pull . second:third 2>err && test_i18ngrep "fetch updated the current branch head" err && - test "$(cat file)" = modified && - test "$(git rev-parse third)" = "$(git rev-parse second)" + echo modified >expect && + test_cmp expect file && + test_cmp_rev third second ' test_expect_success 'fast-forward fails with conflicting work tree' ' git checkout -b third second^ && test_when_finished "git checkout -f copy && git branch -D third" && - test "$(cat file)" = file && + echo file >expect && + test_cmp expect file && echo conflict >file && test_must_fail git pull . second:third 2>err && test_i18ngrep "Cannot fast-forward your working tree" err && - test "$(cat file)" = conflict && - test "$(git rev-parse third)" = "$(git rev-parse second)" + echo conflict >expect && + test_cmp expect file && + test_cmp_rev third second ' test_expect_success '--rebase' ' git branch to-rebase && - echo modified again > file && + echo modified again >file && git commit -m file file && git checkout to-rebase && - echo new > file2 && + echo new >file2 && git add file2 && git commit -m "new file" && git tag before-rebase && git pull --rebase . copy && - test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" + test_cmp_rev HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual ' test_expect_success '--rebase fast forward' ' @@ -263,7 +285,7 @@ test_expect_success '--rebase fast forward' ' git checkout to-rebase && git pull --rebase . ff && - test "$(git rev-parse HEAD)" = "$(git rev-parse ff)" && + test_cmp_rev HEAD ff && # The above only validates the result. Did we actually bypass rebase? git reflog -1 >reflog.actual && @@ -287,7 +309,7 @@ test_expect_success '--rebase --autostash fast forward' ' git checkout behind && echo dirty >file && git pull --rebase --autostash . to-rebase-ff && - test "$(git rev-parse HEAD)" = "$(git rev-parse to-rebase-ff)" + test_cmp_rev HEAD to-rebase-ff ' test_expect_success '--rebase with conflicts shows advice' ' @@ -325,9 +347,11 @@ test_expect_success 'failed --rebase shows advice' ' test_expect_success '--rebase fails with multiple branches' ' git reset --hard before-rebase && test_must_fail git pull --rebase . copy master 2>err && - test "$(git rev-parse HEAD)" = "$(git rev-parse before-rebase)" && + test_cmp_rev HEAD before-rebase && test_i18ngrep "Cannot rebase onto multiple branches" err && - test modified = "$(git show HEAD:file)" + echo modified >expect && + git show HEAD:file >actual && + test_cmp expect actual ' test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' ' @@ -377,8 +401,10 @@ test_expect_success 'pull.rebase' ' git reset --hard before-rebase && test_config pull.rebase true && git pull . copy && - test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" + test_cmp_rev HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual ' test_expect_success 'pull --autostash & pull.rebase=true' ' @@ -395,8 +421,10 @@ test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && test_config branch.to-rebase.rebase true && git pull . copy && - test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" + test_cmp_rev HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual ' test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' @@ -404,23 +432,29 @@ test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' test_config pull.rebase true && test_config branch.to-rebase.rebase false && git pull . copy && - test "$(git rev-parse HEAD^)" != "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" + test_cmp_rev ! HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual ' -test_expect_success "pull --rebase warns on --verify-signatures" ' +test_expect_success 'pull --rebase warns on --verify-signatures' ' git reset --hard before-rebase && git pull --rebase --verify-signatures . copy 2>err && - test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" && + test_cmp_rev HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual && test_i18ngrep "ignoring --verify-signatures for rebase" err ' -test_expect_success "pull --rebase does not warn on --no-verify-signatures" ' +test_expect_success 'pull --rebase does not warn on --no-verify-signatures' ' git reset --hard before-rebase && git pull --rebase --no-verify-signatures . copy 2>err && - test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && - test new = "$(git show HEAD:file2)" && + test_cmp_rev HEAD^ copy && + echo new >expect && + git show HEAD:file2 >actual && + test_cmp expect actual && test_i18ngrep ! "verify-signatures" err ' @@ -440,25 +474,31 @@ test_expect_success 'pull.rebase=false create a new merge commit' ' git reset --hard before-preserve-rebase && test_config pull.rebase false && git pull . copy && - test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^1 before-preserve-rebase && + test_cmp_rev HEAD^2 copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success 'pull.rebase=true flattens keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase true && git pull . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^^ copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase 1 && git pull . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^^ copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success REBASE_P \ @@ -466,8 +506,8 @@ test_expect_success REBASE_P \ git reset --hard before-preserve-rebase && test_config pull.rebase preserve && git pull . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)" + test_cmp_rev HEAD^^ copy && + test_cmp_rev HEAD^2 keep-merge ' test_expect_success 'pull.rebase=interactive' ' @@ -478,7 +518,8 @@ test_expect_success 'pull.rebase=interactive' ' test_set_editor "$TRASH_DIRECTORY/fake-editor" && test_when_finished "test_might_fail git rebase --abort" && test_must_fail git pull --rebase=interactive . copy && - test "I was here" = "$(cat fake.out)" + echo "I was here" >expect && + test_cmp expect fake.out ' test_expect_success 'pull --rebase=i' ' @@ -489,30 +530,35 @@ test_expect_success 'pull --rebase=i' ' test_set_editor "$TRASH_DIRECTORY/fake-editor" && test_when_finished "test_might_fail git rebase --abort" && test_must_fail git pull --rebase=i . copy && - test "I was here, too" = "$(cat fake.out)" + echo "I was here, too" >expect && + test_cmp expect fake.out ' test_expect_success 'pull.rebase=invalid fails' ' git reset --hard before-preserve-rebase && test_config pull.rebase invalid && - ! git pull . copy + test_must_fail git pull . copy ' test_expect_success '--rebase=false create a new merge commit' ' git reset --hard before-preserve-rebase && test_config pull.rebase true && git pull --rebase=false . copy && - test "$(git rev-parse HEAD^1)" = "$(git rev-parse before-preserve-rebase)" && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^1 before-preserve-rebase && + test_cmp_rev HEAD^2 copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success '--rebase=true rebases and flattens keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase preserve && git pull --rebase=true . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^^ copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success REBASE_P \ @@ -520,58 +566,62 @@ test_expect_success REBASE_P \ git reset --hard before-preserve-rebase && test_config pull.rebase true && git pull --rebase=preserve . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)" + test_cmp_rev HEAD^^ copy && + test_cmp_rev HEAD^2 keep-merge ' test_expect_success '--rebase=invalid fails' ' git reset --hard before-preserve-rebase && - ! git pull --rebase=invalid . copy + test_must_fail git pull --rebase=invalid . copy ' test_expect_success '--rebase overrides pull.rebase=preserve and flattens keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase preserve && git pull --rebase . copy && - test "$(git rev-parse HEAD^^)" = "$(git rev-parse copy)" && - test file3 = "$(git show HEAD:file3.t)" + test_cmp_rev HEAD^^ copy && + echo file3 >expect && + git show HEAD:file3.t >actual && + test_cmp expect actual ' test_expect_success '--rebase with rebased upstream' ' - git remote add -f me . && git checkout copy && git tag copy-orig && git reset --hard HEAD^ && - echo conflicting modification > file && + echo conflicting modification >file && git commit -m conflict file && git checkout to-rebase && - echo file > file2 && + echo file >file2 && git commit -m to-rebase file2 && git tag to-rebase-orig && git pull --rebase me copy && - test "conflicting modification" = "$(cat file)" && - test file = "$(cat file2)" - + echo "conflicting modification" >expect && + test_cmp expect file && + echo file >expect && + test_cmp expect file2 ' test_expect_success '--rebase -f with rebased upstream' ' test_when_finished "test_might_fail git rebase --abort" && git reset --hard to-rebase-orig && git pull --rebase -f me copy && - test "conflicting modification" = "$(cat file)" && - test file = "$(cat file2)" + echo "conflicting modification" >expect && + test_cmp expect file && + echo file >expect && + test_cmp expect file2 ' test_expect_success '--rebase with rebased default upstream' ' - git update-ref refs/remotes/me/copy copy-orig && git checkout --track -b to-rebase2 me/copy && git reset --hard to-rebase-orig && git pull --rebase && - test "conflicting modification" = "$(cat file)" && - test file = "$(cat file2)" - + echo "conflicting modification" >expect && + test_cmp expect file && + echo file >expect && + test_cmp expect file2 ' test_expect_success 'rebased upstream + fetch + pull --rebase' ' @@ -582,13 +632,14 @@ test_expect_success 'rebased upstream + fetch + pull --rebase' ' git reset --hard to-rebase-orig && git fetch && git pull --rebase && - test "conflicting modification" = "$(cat file)" && - test file = "$(cat file2)" + echo "conflicting modification" >expect && + test_cmp expect file && + echo file >expect && + test_cmp expect file2 ' test_expect_success 'pull --rebase dies early with dirty working directory' ' - git checkout to-rebase && git update-ref refs/remotes/me/copy copy^ && COPY="$(git rev-parse --verify me/copy)" && @@ -596,23 +647,23 @@ test_expect_success 'pull --rebase dies early with dirty working directory' ' test_config branch.to-rebase.remote me && test_config branch.to-rebase.merge refs/heads/copy && test_config branch.to-rebase.rebase true && - echo dirty >> file && + echo dirty >>file && git add file && test_must_fail git pull && - test "$COPY" = "$(git rev-parse --verify me/copy)" && + test_cmp_rev "$COPY" me/copy && git checkout HEAD -- file && git pull && - test "$COPY" != "$(git rev-parse --verify me/copy)" - + test_cmp_rev ! "$COPY" me/copy ' test_expect_success 'pull --rebase works on branch yet to be born' ' git rev-parse master >expect && mkdir empty_repo && - (cd empty_repo && - git init && - git pull --rebase .. master && - git rev-parse HEAD >../actual + ( + cd empty_repo && + git init && + git pull --rebase .. master && + git rev-parse HEAD >../actual ) && test_cmp expect actual ' @@ -624,10 +675,14 @@ test_expect_success 'pull --rebase fails on unborn branch with staged changes' ' cd empty_repo2 && echo staged-file >staged-file && git add staged-file && - test "$(git ls-files)" = staged-file && + echo staged-file >expect && + git ls-files >actual && + test_cmp expect actual && test_must_fail git pull --rebase .. master 2>err && - test "$(git ls-files)" = staged-file && - test "$(git show :staged-file)" = staged-file && + git ls-files >actual && + test_cmp expect actual && + git show :staged-file >actual && + test_cmp expect actual && test_i18ngrep "unborn branch with changes added to the index" err ) ' @@ -638,7 +693,8 @@ test_expect_success 'pull --rebase fails on corrupt HEAD' ' ( cd corrupt && test_commit one && - obj=$(git rev-parse --verify HEAD | sed "s#^..#&/#") && + git rev-parse --verify HEAD >head && + obj=$(sed "s#^..#&/#" head) && rm -f .git/objects/$obj && test_must_fail git pull --rebase ) @@ -646,66 +702,77 @@ test_expect_success 'pull --rebase fails on corrupt HEAD' ' test_expect_success 'setup for detecting upstreamed changes' ' mkdir src && - (cd src && - git init && - printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff && - git add stuff && - git commit -m "Initial revision" + ( + cd src && + git init && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" > stuff && + git add stuff && + git commit -m "Initial revision" ) && git clone src dst && - (cd src && - modify s/5/43/ stuff && - git commit -a -m "5->43" && - modify s/6/42/ stuff && - git commit -a -m "Make it bigger" + ( + cd src && + modify s/5/43/ stuff && + git commit -a -m "5->43" && + modify s/6/42/ stuff && + git commit -a -m "Make it bigger" ) && - (cd dst && - modify s/5/43/ stuff && - git commit -a -m "Independent discovery of 5->43" + ( + cd dst && + modify s/5/43/ stuff && + git commit -a -m "Independent discovery of 5->43" ) ' test_expect_success 'git pull --rebase detects upstreamed changes' ' - (cd dst && - git pull --rebase && - test -z "$(git ls-files -u)" + ( + cd dst && + git pull --rebase && + git ls-files -u >untracked && + test_must_be_empty untracked ) ' test_expect_success 'setup for avoiding reapplying old patches' ' - (cd dst && - test_might_fail git rebase --abort && - git reset --hard origin/master + ( + cd dst && + test_might_fail git rebase --abort && + git reset --hard origin/master ) && git clone --bare src src-replace.git && rm -rf src && mv src-replace.git src && - (cd dst && - modify s/2/22/ stuff && - git commit -a -m "Change 2" && - modify s/3/33/ stuff && - git commit -a -m "Change 3" && - modify s/4/44/ stuff && - git commit -a -m "Change 4" && - git push && - - modify s/44/55/ stuff && - git commit --amend -a -m "Modified Change 4" + ( + cd dst && + modify s/2/22/ stuff && + git commit -a -m "Change 2" && + modify s/3/33/ stuff && + git commit -a -m "Change 3" && + modify s/4/44/ stuff && + git commit -a -m "Change 4" && + git push && + + modify s/44/55/ stuff && + git commit --amend -a -m "Modified Change 4" ) ' test_expect_success 'git pull --rebase does not reapply old patches' ' - (cd dst && - test_must_fail git pull --rebase && - test 1 = $(find .git/rebase-apply -name "000*" | wc -l) + ( + cd dst && + test_must_fail git pull --rebase && + find .git/rebase-apply -name "000*" >patches && + test_line_count = 1 patches ) ' test_expect_success 'git pull --rebase against local branch' ' git checkout -b copy2 to-rebase-orig && git pull --rebase . to-rebase && - test "conflicting modification" = "$(cat file)" && - test file = "$(cat file2)" + echo "conflicting modification" >expect && + test_cmp expect file && + echo file >expect && + test_cmp expect file2 ' test_done diff --git a/t/t5608-clone-2gb.sh b/t/t5608-clone-2gb.sh index 2c6bc07344..eee0842888 100755 --- a/t/t5608-clone-2gb.sh +++ b/t/t5608-clone-2gb.sh @@ -3,7 +3,7 @@ test_description='Test cloning a repository larger than 2 gigabyte' . ./test-lib.sh -if test -z "$GIT_TEST_CLONE_2GB" +if ! test_bool_env GIT_TEST_CLONE_2GB false then say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t' else diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 3a2c143c6d..1424fabd4a 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -18,14 +18,16 @@ get_actual_commits () { p }' <out | test-tool pkt-line unpack-sideband >o.pack && git index-pack o.pack && - git verify-pack -v o.idx | grep commit | cut -c-40 | sort >actual_commits + git verify-pack -v o.idx >objs && + grep commit objs | cut -c-40 | sort >actual_commits } check_output () { get_actual_refs && test_cmp expected_refs actual_refs && get_actual_commits && - test_cmp expected_commits actual_commits + sort expected_commits >sorted_commits && + test_cmp sorted_commits actual_commits } # c(o/foo) d(o/bar) @@ -75,17 +77,19 @@ test_expect_success 'invalid want-ref line' ' ' test_expect_success 'basic want-ref' ' + oid=$(git rev-parse f) && cat >expected_refs <<-EOF && - $(git rev-parse f) refs/heads/master + $oid refs/heads/master EOF - git rev-parse f | sort >expected_commits && + git rev-parse f >expected_commits && + oid=$(git rev-parse a) && test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress want-ref refs/heads/master - have $(git rev-parse a) + have $oid done 0000 EOF @@ -95,19 +99,22 @@ test_expect_success 'basic want-ref' ' ' test_expect_success 'multiple want-ref lines' ' + oid_c=$(git rev-parse c) && + oid_d=$(git rev-parse d) && cat >expected_refs <<-EOF && - $(git rev-parse c) refs/heads/o/foo - $(git rev-parse d) refs/heads/o/bar + $oid_c refs/heads/o/foo + $oid_d refs/heads/o/bar EOF - git rev-parse c d | sort >expected_commits && + git rev-parse c d >expected_commits && + oid=$(git rev-parse b) && test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress want-ref refs/heads/o/foo want-ref refs/heads/o/bar - have $(git rev-parse b) + have $oid done 0000 EOF @@ -117,10 +124,11 @@ test_expect_success 'multiple want-ref lines' ' ' test_expect_success 'mix want and want-ref' ' + oid=$(git rev-parse f) && cat >expected_refs <<-EOF && - $(git rev-parse f) refs/heads/master + $oid refs/heads/master EOF - git rev-parse e f | sort >expected_commits && + git rev-parse e f >expected_commits && test-tool pkt-line pack >in <<-EOF && command=fetch @@ -138,17 +146,19 @@ test_expect_success 'mix want and want-ref' ' ' test_expect_success 'want-ref with ref we already have commit for' ' + oid=$(git rev-parse c) && cat >expected_refs <<-EOF && - $(git rev-parse c) refs/heads/o/foo + $oid refs/heads/o/foo EOF >expected_commits && + oid=$(git rev-parse c) && test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress want-ref refs/heads/o/foo - have $(git rev-parse c) + have $oid done 0000 EOF @@ -211,13 +221,14 @@ test_expect_success 'fetching with exact OID' ' rm -rf local && cp -r "$LOCAL_PRISTINE" local && + oid=$(git -C "$REPO" rev-parse d) && GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ - $(git -C "$REPO" rev-parse d):refs/heads/actual && + "$oid":refs/heads/actual && git -C "$REPO" rev-parse "d" >expected && git -C local rev-parse refs/heads/actual >actual && test_cmp expected actual && - grep "want $(git -C "$REPO" rev-parse d)" log + grep "want $oid" log ' test_expect_success 'fetching multiple refs' ' @@ -239,13 +250,14 @@ test_expect_success 'fetching ref and exact OID' ' rm -rf local && cp -r "$LOCAL_PRISTINE" local && + oid=$(git -C "$REPO" rev-parse b) && GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ - master $(git -C "$REPO" rev-parse b):refs/heads/actual && + master "$oid":refs/heads/actual && git -C "$REPO" rev-parse "master" "b" >expected && git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual && test_cmp expected actual && - grep "want $(git -C "$REPO" rev-parse b)" log && + grep "want $oid" log && grep "want-ref refs/heads/master" log ' @@ -312,10 +324,9 @@ inconsistency () { # repository appears to change during negotiation, for example, when # different servers in a load-balancing arrangement serve (stateless) # RPCs during a single negotiation. - printf "s/%s/%s/" \ - $(git -C "$REPO" rev-parse $1 | tr -d "\n") \ - $(git -C "$REPO" rev-parse $2 | tr -d "\n") \ - >"$HTTPD_ROOT_PATH/one-time-sed" + oid1=$(git -C "$REPO" rev-parse $1) && + oid2=$(git -C "$REPO" rev-parse $2) && + echo "s/$oid1/$oid2/" >"$HTTPD_ROOT_PATH/one-time-sed" } test_expect_success 'server is initially ahead - no ref in want' ' diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index beadaf6cca..353f84313f 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -143,14 +143,14 @@ test_expect_success 'setup criss-cross' ' test_expect_success 'criss-cross: rev-list --ancestry-path cb..bc' ' (cd criss-cross && git rev-list --ancestry-path xcb..xbc > actual && - test -z "$(cat actual)") + test_must_be_empty actual) ' # no commits in repository descend from cb test_expect_success 'criss-cross: rev-list --ancestry-path --all ^cb' ' (cd criss-cross && git rev-list --ancestry-path --all ^xcb > actual && - test -z "$(cat actual)") + test_must_be_empty actual) ' test_done diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh index 213deecab1..d254e020b6 100755 --- a/t/t6021-merge-criss-cross.sh +++ b/t/t6021-merge-criss-cross.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Fredrik Kuivinen # -# See http://marc.info/?l=git&m=111463358500362&w=2 for a +# See https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org/ for a # nice description of what this is about. diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh index 37760233a5..ba7902c9cd 100755 --- a/t/t6130-pathspec-noglob.sh +++ b/t/t6130-pathspec-noglob.sh @@ -10,6 +10,7 @@ test_expect_success 'create commits with glob characters' ' # the name "f*" in the worktree, because it is not allowed # on Windows (the tests below do not depend on the presence # of the file in the worktree) + git config core.protectNTFS false && git update-index --add --cacheinfo 100644 "$(git rev-parse HEAD:foo)" "f*" && test_tick && git commit -m star && diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index c0f04dc6b0..0a69a67117 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -103,14 +103,14 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre ' test_expect_success 'gc --no-quiet' ' - git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + GIT_PROGRESS_DELAY=0 git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && test_must_be_empty stdout && - test_line_count = 1 stderr && test_i18ngrep "Computing commit graph generation numbers" stderr ' test_expect_success TTY 'with TTY: gc --no-quiet' ' - test_terminal git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + test_terminal env GIT_PROGRESS_DELAY=0 \ + git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && test_must_be_empty stdout && test_i18ngrep "Enumerating objects" stderr && test_i18ngrep "Computing commit graph generation numbers" stderr diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh index bd10a96727..fc2a6cf5c7 100755 --- a/t/t7105-reset-patch.sh +++ b/t/t7105-reset-patch.sh @@ -38,6 +38,27 @@ test_expect_success PERL 'git reset -p HEAD^' ' test_i18ngrep "Apply" output ' +test_expect_success PERL 'git reset -p HEAD^^{tree}' ' + test_write_lines n y | git reset -p HEAD^^{tree} >output && + verify_state dir/foo work parent && + verify_saved_state bar && + test_i18ngrep "Apply" output +' + +test_expect_success PERL 'git reset -p HEAD^:dir/foo (blob fails)' ' + set_and_save_state dir/foo work work && + test_must_fail git reset -p HEAD^:dir/foo && + verify_saved_state dir/foo && + verify_saved_state bar +' + +test_expect_success PERL 'git reset -p aaaaaaaa (unknown fails)' ' + set_and_save_state dir/foo work work && + test_must_fail git reset -p aaaaaaaa && + verify_saved_state dir/foo && + verify_saved_state bar +' + # The idea in the rest is that bar sorts first, so we always say 'y' # first and if the path limiter fails it'll apply to bar instead of # dir/foo. There's always an extra 'n' to reject edits to dir/foo in diff --git a/t/t7107-reset-pathspec-file.sh b/t/t7107-reset-pathspec-file.sh new file mode 100755 index 0000000000..6b1a731fff --- /dev/null +++ b/t/t7107-reset-pathspec-file.sh @@ -0,0 +1,155 @@ +#!/bin/sh + +test_description='reset --pathspec-from-file' + +. ./test-lib.sh + +test_tick + +test_expect_success setup ' + echo A >fileA.t && + echo B >fileB.t && + echo C >fileC.t && + echo D >fileD.t && + git add . && + git commit --include . -m "Commit" && + git tag checkpoint +' + +restore_checkpoint () { + git reset --hard checkpoint +} + +verify_expect () { + git status --porcelain -- fileA.t fileB.t fileC.t fileD.t >actual && + test_cmp expect actual +} + +test_expect_success '--pathspec-from-file from stdin' ' + restore_checkpoint && + + git rm fileA.t && + echo fileA.t | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + EOF + verify_expect +' + +test_expect_success '--pathspec-from-file from file' ' + restore_checkpoint && + + git rm fileA.t && + echo fileA.t >list && + git reset --pathspec-from-file=list && + + cat >expect <<-\EOF && + D fileA.t + EOF + verify_expect +' + +test_expect_success 'NUL delimiters' ' + restore_checkpoint && + + git rm fileA.t fileB.t && + printf "fileA.t\0fileB.t\0" | git reset --pathspec-from-file=- --pathspec-file-nul && + + cat >expect <<-\EOF && + D fileA.t + D fileB.t + EOF + verify_expect +' + +test_expect_success 'LF delimiters' ' + restore_checkpoint && + + git rm fileA.t fileB.t && + printf "fileA.t\nfileB.t\n" | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + D fileB.t + EOF + verify_expect +' + +test_expect_success 'no trailing delimiter' ' + restore_checkpoint && + + git rm fileA.t fileB.t && + printf "fileA.t\nfileB.t" | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + D fileB.t + EOF + verify_expect +' + +test_expect_success 'CRLF delimiters' ' + restore_checkpoint && + + git rm fileA.t fileB.t && + printf "fileA.t\r\nfileB.t\r\n" | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + D fileB.t + EOF + verify_expect +' + +test_expect_success 'quotes' ' + restore_checkpoint && + + git rm fileA.t && + printf "\"file\\101.t\"" | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + EOF + verify_expect +' + +test_expect_success 'quotes not compatible with --pathspec-file-nul' ' + restore_checkpoint && + + git rm fileA.t && + printf "\"file\\101.t\"" >list && + # Note: "git reset" has not yet learned to fail on wrong pathspecs + git reset --pathspec-from-file=list --pathspec-file-nul && + + cat >expect <<-\EOF && + D fileA.t + EOF + test_must_fail verify_expect +' + +test_expect_success '--pathspec-from-file is not compatible with --soft or --hard' ' + restore_checkpoint && + + git rm fileA.t && + echo fileA.t >list && + test_must_fail git reset --soft --pathspec-from-file=list && + test_must_fail git reset --hard --pathspec-from-file=list +' + +test_expect_success 'only touches what was listed' ' + restore_checkpoint && + + git rm fileA.t fileB.t fileC.t fileD.t && + printf "fileB.t\nfileC.t\n" | git reset --pathspec-from-file=- && + + cat >expect <<-\EOF && + D fileA.t + D fileB.t + D fileC.t + D fileD.t + EOF + verify_expect +' + +test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 691b5fc3bf..7f75bb1be6 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -356,6 +356,28 @@ test_expect_success 'status should only print one line' ' test_line_count = 1 lines ' +test_expect_success 'status from subdirectory should have the same SHA1' ' + test_when_finished "rmdir addtest/subdir" && + ( + cd addtest && + mkdir subdir && + git submodule status >output && + awk "{print \$1}" <output >expect && + cd subdir && + git submodule status >../output && + awk "{print \$1}" <../output >../actual && + test_cmp ../expect ../actual && + git -C ../submod checkout HEAD^ && + git submodule status >../output && + awk "{print \$1}" <../output >../actual2 && + cd .. && + git submodule status >output && + awk "{print \$1}" <output >expect2 && + test_cmp expect2 actual2 && + ! test_cmp actual actual2 + ) +' + test_expect_success 'setup - fetch commit name from submodule' ' rev1=$(cd .subrepo && git rev-parse HEAD) && printf "rev1: %s\n" "$rev1" && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index df34c994d2..7478f7ab7e 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -406,12 +406,26 @@ test_expect_success 'submodule update - command in .git/config' ' ) ' -test_expect_success 'submodule update - command in .gitmodules is ignored' ' +test_expect_success 'submodule update - command in .gitmodules is rejected' ' test_when_finished "git -C super reset --hard HEAD^" && git -C super config -f .gitmodules submodule.submodule.update "!false" && git -C super commit -a -m "add command to .gitmodules file" && git -C super/submodule reset --hard $submodulesha1^ && - git -C super submodule update submodule + test_must_fail git -C super submodule update submodule +' + +test_expect_success 'fsck detects command in .gitmodules' ' + git init command-in-gitmodules && + ( + cd command-in-gitmodules && + git submodule add ../submodule submodule && + test_commit adding-submodule && + + git config -f .gitmodules submodule.submodule.update "!false" && + git add .gitmodules && + test_commit configuring-update && + test_must_fail git fsck + ) ' cat << EOF >expect @@ -480,6 +494,9 @@ test_expect_success 'recursive submodule update - command in .git/config catches ' test_expect_success 'submodule init does not copy command into .git/config' ' + test_when_finished "git -C super update-index --force-remove submodule1" && + test_when_finished git config -f super/.gitmodules \ + --remove-section submodule.submodule1 && (cd super && git ls-files -s submodule >out && H=$(cut -d" " -f2 out) && @@ -488,10 +505,9 @@ test_expect_success 'submodule init does not copy command into .git/config' ' git config -f .gitmodules submodule.submodule1.path submodule1 && git config -f .gitmodules submodule.submodule1.url ../submodule && git config -f .gitmodules submodule.submodule1.update !false && - git submodule init submodule1 && - echo "none" >expect && - git config submodule.submodule1.update >actual && - test_cmp expect actual + test_must_fail git submodule init submodule1 && + test_expect_code 1 git config submodule.submodule1.update >actual && + test_must_be_empty actual ) ' diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh index 49a37efe9c..905a557585 100755 --- a/t/t7415-submodule-names.sh +++ b/t/t7415-submodule-names.sh @@ -191,4 +191,60 @@ test_expect_success 'fsck detects corrupt .gitmodules' ' ) ' +test_expect_success MINGW 'prevent git~1 squatting on Windows' ' + git init squatting && + ( + cd squatting && + mkdir a && + touch a/..git && + git add a/..git && + test_tick && + git commit -m initial && + + modules="$(test_write_lines \ + "[submodule \"b.\"]" "url = ." "path = c" \ + "[submodule \"b\"]" "url = ." "path = d\\\\a" | + git hash-object -w --stdin)" && + rev="$(git rev-parse --verify HEAD)" && + hash="$(echo x | git hash-object -w --stdin)" && + git -c core.protectNTFS=false update-index --add \ + --cacheinfo 100644,$modules,.gitmodules \ + --cacheinfo 160000,$rev,c \ + --cacheinfo 160000,$rev,d\\a \ + --cacheinfo 100644,$hash,d./a/x \ + --cacheinfo 100644,$hash,d./a/..git && + test_tick && + git -c core.protectNTFS=false commit -m "module" && + test_must_fail git show HEAD: 2>err && + test_i18ngrep backslash err + ) && + test_must_fail git -c core.protectNTFS=false \ + clone --recurse-submodules squatting squatting-clone 2>err && + test_i18ngrep -e "directory not empty" -e "not an empty directory" err && + ! grep gitdir squatting-clone/d/a/git~2 +' + +test_expect_success 'git dirs of sibling submodules must not be nested' ' + git init nested && + test_commit -C nested nested && + ( + cd nested && + cat >.gitmodules <<-EOF && + [submodule "hippo"] + url = . + path = thing1 + [submodule "hippo/hooks"] + url = . + path = thing2 + EOF + git clone . thing1 && + git clone . thing2 && + git add .gitmodules thing1 thing2 && + test_tick && + git commit -m nested + ) && + test_must_fail git clone --recurse-submodules nested clone 2>err && + test_i18ngrep "is inside git dir" err +' + test_done diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh index 1cd2c1c1ea..5ba041f537 100755 --- a/t/t7416-submodule-dash-url.sh +++ b/t/t7416-submodule-dash-url.sh @@ -46,4 +46,18 @@ test_expect_success 'fsck rejects unprotected dash' ' grep gitmodulesUrl err ' +test_expect_success 'trailing backslash is handled correctly' ' + git init testmodule && + test_commit -C testmodule c && + git submodule add ./testmodule && + : ensure that the name ends in a double backslash && + sed -e "s|\\(submodule \"testmodule\\)\"|\\1\\\\\\\\\"|" \ + -e "s|url = .*|url = \" --should-not-be-an-option\"|" \ + <.gitmodules >.new && + mv .new .gitmodules && + git commit -am "Add testmodule" && + test_must_fail git clone --verbose --recurse-submodules . dolly 2>err && + test_i18ngrep ! "unknown option" err +' + test_done diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh index 756af8c4d6..f7e7e94d7b 100755 --- a/t/t7417-submodule-path-url.sh +++ b/t/t7417-submodule-path-url.sh @@ -25,4 +25,21 @@ test_expect_success 'fsck rejects unprotected dash' ' grep gitmodulesPath err ' +test_expect_success MINGW 'submodule paths disallows trailing spaces' ' + git init super && + test_must_fail git -C super submodule add ../upstream "sub " && + + : add "sub", then rename "sub" to "sub ", the hard way && + git -C super submodule add ../upstream sub && + tree=$(git -C super write-tree) && + git -C super ls-tree $tree >tree && + sed "s/sub/sub /" <tree >tree.new && + tree=$(git -C super mktree <tree.new) && + commit=$(echo with space | git -C super commit-tree $tree) && + git -C super update-ref refs/heads/master $commit && + + test_must_fail git clone --recurse-submodules super dst 2>err && + test_i18ngrep "sub " err +' + test_done diff --git a/t/t7420-submodule-set-url.sh b/t/t7420-submodule-set-url.sh new file mode 100755 index 0000000000..ef0cb6e8e1 --- /dev/null +++ b/t/t7420-submodule-set-url.sh @@ -0,0 +1,55 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='Test submodules set-url subcommand + +This test verifies that the set-url subcommand of git-submodule is working +as expected. +' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'submodule config cache setup' ' + mkdir submodule && + ( + cd submodule && + git init && + echo a >file && + git add file && + git commit -ma + ) && + mkdir super && + ( + cd super && + git init && + git submodule add ../submodule && + git commit -m "add submodule" + ) +' + +test_expect_success 'test submodule set-url' ' + # add a commit and move the submodule (change the url) + ( + cd submodule && + echo b >>file && + git add file && + git commit -mb + ) && + mv submodule newsubmodule && + + git -C newsubmodule show >expect && + ( + cd super && + test_must_fail git submodule update --remote && + git submodule set-url submodule ../newsubmodule && + grep -F "url = ../newsubmodule" .gitmodules && + git submodule update --remote + ) && + git -C super/submodule show >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh index f1349af56e..110b4bf459 100755 --- a/t/t7501-commit-basic-functionality.sh +++ b/t/t7501-commit-basic-functionality.sh @@ -150,7 +150,7 @@ test_expect_success 'setup: commit message from file' ' test_expect_success 'amend commit' ' cat >editor <<-\EOF && #!/bin/sh - sed -e "s/a file/an amend commit/g" < "$1" > "$1-" + sed -e "s/a file/an amend commit/g" <"$1" >"$1-" mv "$1-" "$1" EOF chmod 755 editor && @@ -263,7 +263,7 @@ test_expect_success 'using message from other commit' ' test_expect_success 'editing message from other commit' ' cat >editor <<-\EOF && #!/bin/sh - sed -e "s/amend/older/g" < "$1" > "$1-" + sed -e "s/amend/older/g" <"$1" >"$1-" mv "$1-" "$1" EOF chmod 755 editor && @@ -285,9 +285,8 @@ test_expect_success 'overriding author from command line' ' ' test_expect_success PERL 'interactive add' ' - echo 7 | - git commit --interactive | - grep "What now" + echo 7 | test_must_fail git commit --interactive >out && + grep "What now" out ' test_expect_success PERL "commit --interactive doesn't change index if editor aborts" ' @@ -362,12 +361,12 @@ test_expect_success 'amend commit to fix author' ' oldtick=$GIT_AUTHOR_DATE && test_tick && git reset --hard && - git cat-file -p HEAD | + git cat-file -p HEAD >commit && sed -e "s/author.*/author $author $oldtick/" \ - -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ - expected && + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \ + commit >expected && git commit --amend --author="$author" && - git cat-file -p HEAD > current && + git cat-file -p HEAD >current && test_cmp expected current ' @@ -377,12 +376,12 @@ test_expect_success 'amend commit to fix date' ' test_tick && newtick=$GIT_AUTHOR_DATE && git reset --hard && - git cat-file -p HEAD | + git cat-file -p HEAD >commit && sed -e "s/author.*/author $author $newtick/" \ - -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ - expected && + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \ + commit >expected && git commit --amend --date="$newtick" && - git cat-file -p HEAD > current && + git cat-file -p HEAD >current && test_cmp expected current ' @@ -409,12 +408,13 @@ test_expect_success 'sign off (1)' ' echo 1 >positive && git add positive && git commit -s -m "thank you" && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo thank you && echo && - git var GIT_COMMITTER_IDENT | - sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + git var GIT_COMMITTER_IDENT >ident && + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident ) >expected && test_cmp expected actual @@ -428,13 +428,14 @@ test_expect_success 'sign off (2)' ' git commit -s -m "thank you $existing" && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo thank you && echo && echo $existing && - git var GIT_COMMITTER_IDENT | - sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + git var GIT_COMMITTER_IDENT >ident && + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident ) >expected && test_cmp expected actual @@ -448,13 +449,14 @@ test_expect_success 'signoff gap' ' git commit -s -m "welcome $alt" && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo welcome && echo && echo $alt && - git var GIT_COMMITTER_IDENT | - sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + git var GIT_COMMITTER_IDENT >ident && + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident ) >expected && test_cmp expected actual ' @@ -468,15 +470,16 @@ test_expect_success 'signoff gap 2' ' We have now $alt" && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo welcome && echo && echo We have now && echo $alt && echo && - git var GIT_COMMITTER_IDENT | - sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" + git var GIT_COMMITTER_IDENT >ident && + sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ident ) >expected && test_cmp expected actual ' @@ -489,7 +492,8 @@ test_expect_success 'signoff respects trailer config' ' non-trailer line Myfooter: x" && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo subject && echo && @@ -506,7 +510,8 @@ Myfooter: x" && non-trailer line Myfooter: x" && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo subject && echo && @@ -538,7 +543,8 @@ test_expect_success 'multiple -m' ' >negative && git add negative && git commit -m "one" -m "two" -m "three" && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + git cat-file commit HEAD >commit && + sed -e "1,/^\$/d" commit >actual && ( echo one && echo && @@ -555,23 +561,25 @@ test_expect_success 'amend commit to fix author' ' oldtick=$GIT_AUTHOR_DATE && test_tick && git reset --hard && - git cat-file -p HEAD | + git cat-file -p HEAD >commit && sed -e "s/author.*/author $author $oldtick/" \ - -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \ - expected && + -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" \ + commit >expected && git commit --amend --author="$author" && - git cat-file -p HEAD > current && + git cat-file -p HEAD >current && test_cmp expected current ' test_expect_success 'git commit <file> with dirty index' ' - echo tacocat > elif && - echo tehlulz > chz && + echo tacocat >elif && + echo tehlulz >chz && git add chz && git commit elif -m "tacocat is a palindrome" && - git show --stat | grep elif && - git diff --cached | grep chz + git show --stat >stat && + grep elif stat && + git diff --cached >diff && + grep chz diff ' test_expect_success 'same tree (single parent)' ' @@ -584,7 +592,8 @@ test_expect_success 'same tree (single parent)' ' test_expect_success 'same tree (single parent) --allow-empty' ' git commit --allow-empty -m "forced empty" && - git cat-file commit HEAD | grep forced + git cat-file commit HEAD >commit && + grep forced commit ' diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index 1e47ed2ca2..cf0fda2d5a 100755 --- a/t/t7519-status-fsmonitor.sh +++ b/t/t7519-status-fsmonitor.sh @@ -106,6 +106,8 @@ EOF # test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' ' + write_script .git/hooks/fsmonitor-test<<-\EOF && + EOF git update-index --fsmonitor && git update-index --fsmonitor-valid dir1/modified && git update-index --fsmonitor-valid dir2/modified && @@ -164,6 +166,8 @@ EOF # test that newly added files are marked valid test_expect_success 'newly added files are marked valid' ' + write_script .git/hooks/fsmonitor-test<<-\EOF && + EOF git add new && git add dir1/new && git add dir2/new && @@ -218,11 +222,12 @@ test_expect_success '*only* files returned by the integration script get flagged # Ensure commands that call refresh_index() to move the index back in time # properly invalidate the fsmonitor cache test_expect_success 'refresh_index() invalidates fsmonitor cache' ' - write_script .git/hooks/fsmonitor-test<<-\EOF && - EOF clean_repo && dirty_repo && + write_integration_script && git add . && + write_script .git/hooks/fsmonitor-test<<-\EOF && + EOF git commit -m "to reset" && git reset HEAD~1 && git status >actual && diff --git a/t/t7526-commit-pathspec-file.sh b/t/t7526-commit-pathspec-file.sh new file mode 100755 index 0000000000..a06b683534 --- /dev/null +++ b/t/t7526-commit-pathspec-file.sh @@ -0,0 +1,130 @@ +#!/bin/sh + +test_description='commit --pathspec-from-file' + +. ./test-lib.sh + +test_tick + +test_expect_success setup ' + test_commit file0 && + git tag checkpoint && + + echo A >fileA.t && + echo B >fileB.t && + echo C >fileC.t && + echo D >fileD.t && + git add fileA.t fileB.t fileC.t fileD.t +' + +restore_checkpoint () { + git reset --soft checkpoint +} + +verify_expect () { + git diff-tree --no-commit-id --name-status -r HEAD >actual && + test_cmp expect actual +} + +test_expect_success '--pathspec-from-file from stdin' ' + restore_checkpoint && + + echo fileA.t | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + EOF + verify_expect +' + +test_expect_success '--pathspec-from-file from file' ' + restore_checkpoint && + + echo fileA.t >list && + git commit --pathspec-from-file=list -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + EOF + verify_expect +' + +test_expect_success 'NUL delimiters' ' + restore_checkpoint && + + printf "fileA.t\0fileB.t\0" | git commit --pathspec-from-file=- --pathspec-file-nul -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + A fileB.t + EOF + verify_expect +' + +test_expect_success 'LF delimiters' ' + restore_checkpoint && + + printf "fileA.t\nfileB.t\n" | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + A fileB.t + EOF + verify_expect +' + +test_expect_success 'no trailing delimiter' ' + restore_checkpoint && + + printf "fileA.t\nfileB.t" | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + A fileB.t + EOF + verify_expect +' + +test_expect_success 'CRLF delimiters' ' + restore_checkpoint && + + printf "fileA.t\r\nfileB.t\r\n" | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + A fileB.t + EOF + verify_expect +' + +test_expect_success 'quotes' ' + restore_checkpoint && + + printf "\"file\\101.t\"" | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileA.t + EOF + verify_expect expect +' + +test_expect_success 'quotes not compatible with --pathspec-file-nul' ' + restore_checkpoint && + + printf "\"file\\101.t\"" >list && + test_must_fail git commit --pathspec-from-file=list --pathspec-file-nul -m "Commit" +' + +test_expect_success 'only touches what was listed' ' + restore_checkpoint && + + printf "fileB.t\nfileC.t\n" | git commit --pathspec-from-file=- -m "Commit" && + + cat >expect <<-\EOF && + A fileB.t + A fileC.t + EOF + verify_expect +' + +test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 4e855bc21b..25b235c063 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -4,129 +4,104 @@ test_description='git repack works correctly' . ./test-lib.sh -commit_and_pack() { - test_commit "$@" >/dev/null && - SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) && - echo pack-${SHA1}.pack +commit_and_pack () { + test_commit "$@" 1>&2 && + incrpackid=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) && + echo pack-${incrpackid}.pack +} + +test_no_missing_in_packs () { + myidx=$(ls -1 .git/objects/pack/*.idx) && + test_path_is_file "$myidx" && + git verify-pack -v alt_objects/pack/*.idx >orig.raw && + sed -n -e "s/^\($OID_REGEX\).*/\1/p" orig.raw | sort >orig && + git verify-pack -v $myidx >dest.raw && + cut -d" " -f1 dest.raw | sort >dest && + comm -23 orig dest >missing && + test_must_be_empty missing +} + +# we expect $packid and $oid to be defined +test_has_duplicate_object () { + want_duplicate_object="$1" + found_duplicate_object=false + for p in .git/objects/pack/*.idx + do + idx=$(basename $p) + test "pack-$packid.idx" = "$idx" && continue + git verify-pack -v $p >packlist || return $? + if grep "^$oid" packlist + then + found_duplicate_object=true + echo "DUPLICATE OBJECT FOUND" + break + fi + done && + test "$want_duplicate_object" = "$found_duplicate_object" } test_expect_success 'objects in packs marked .keep are not repacked' ' - echo content1 > file1 && - echo content2 > file2 && + echo content1 >file1 && + echo content2 >file2 && git add . && test_tick && git commit -m initial_commit && # Create two packs # The first pack will contain all of the objects except one - git rev-list --objects --all | grep -v file2 | - git pack-objects pack > /dev/null && + git rev-list --objects --all >objs && + grep -v file2 objs | git pack-objects pack && # The second pack will contain the excluded object - packsha1=$(git rev-list --objects --all | grep file2 | - git pack-objects pack) && - >pack-$packsha1.keep && - objsha1=$(git verify-pack -v pack-$packsha1.idx | head -n 1 | - sed -e "s/^\([0-9a-f]\{40\}\).*/\1/") && + packid=$(grep file2 objs | git pack-objects pack) && + >pack-$packid.keep && + git verify-pack -v pack-$packid.idx >packlist && + oid=$(head -n 1 packlist | sed -e "s/^\($OID_REGEX\).*/\1/") && mv pack-* .git/objects/pack/ && git repack -A -d -l && git prune-packed && - for p in .git/objects/pack/*.idx; do - idx=$(basename $p) - test "pack-$packsha1.idx" = "$idx" && continue - if git verify-pack -v $p | egrep "^$objsha1"; then - found_duplicate_object=1 - echo "DUPLICATE OBJECT FOUND" - break - fi - done && - test -z "$found_duplicate_object" + test_has_duplicate_object false ' test_expect_success 'writing bitmaps via command-line can duplicate .keep objects' ' - # build on $objsha1, $packsha1, and .keep state from previous + # build on $oid, $packid, and .keep state from previous git repack -Adbl && - test_when_finished "found_duplicate_object=" && - for p in .git/objects/pack/*.idx; do - idx=$(basename $p) - test "pack-$packsha1.idx" = "$idx" && continue - if git verify-pack -v $p | egrep "^$objsha1"; then - found_duplicate_object=1 - echo "DUPLICATE OBJECT FOUND" - break - fi - done && - test "$found_duplicate_object" = 1 + test_has_duplicate_object true ' test_expect_success 'writing bitmaps via config can duplicate .keep objects' ' - # build on $objsha1, $packsha1, and .keep state from previous + # build on $oid, $packid, and .keep state from previous git -c repack.writebitmaps=true repack -Adl && - test_when_finished "found_duplicate_object=" && - for p in .git/objects/pack/*.idx; do - idx=$(basename $p) - test "pack-$packsha1.idx" = "$idx" && continue - if git verify-pack -v $p | egrep "^$objsha1"; then - found_duplicate_object=1 - echo "DUPLICATE OBJECT FOUND" - break - fi - done && - test "$found_duplicate_object" = 1 + test_has_duplicate_object true ' test_expect_success 'loose objects in alternate ODB are not repacked' ' mkdir alt_objects && - echo $(pwd)/alt_objects > .git/objects/info/alternates && - echo content3 > file3 && - objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) && + echo $(pwd)/alt_objects >.git/objects/info/alternates && + echo content3 >file3 && + oid=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) && git add file3 && test_tick && git commit -m commit_file3 && git repack -a -d -l && git prune-packed && - for p in .git/objects/pack/*.idx; do - if git verify-pack -v $p | egrep "^$objsha1"; then - found_duplicate_object=1 - echo "DUPLICATE OBJECT FOUND" - break - fi - done && - test -z "$found_duplicate_object" + test_has_duplicate_object false ' test_expect_success 'packed obs in alt ODB are repacked even when local repo is packless' ' mkdir alt_objects/pack && mv .git/objects/pack/* alt_objects/pack && git repack -a && - myidx=$(ls -1 .git/objects/pack/*.idx) && - test -f "$myidx" && - for p in alt_objects/pack/*.idx; do - git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p" - done | while read sha1 rest; do - if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then - echo "Missing object in local pack: $sha1" - return 1 - fi - done + test_no_missing_in_packs ' test_expect_success 'packed obs in alt ODB are repacked when local repo has packs' ' rm -f .git/objects/pack/* && - echo new_content >> file1 && + echo new_content >>file1 && git add file1 && test_tick && git commit -m more_content && git repack && git repack -a -d && - myidx=$(ls -1 .git/objects/pack/*.idx) && - test -f "$myidx" && - for p in alt_objects/pack/*.idx; do - git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p" - done | while read sha1 rest; do - if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then - echo "Missing object in local pack: $sha1" - return 1 - fi - done + test_no_missing_in_packs ' test_expect_success 'packed obs in alternate ODB kept pack are repacked' ' @@ -134,7 +109,7 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' ' for p in alt_objects/pack/*.pack do base_name=$(basename $p .pack) && - if test -f alt_objects/pack/$base_name.keep + if test_path_is_file alt_objects/pack/$base_name.keep then rm alt_objects/pack/$base_name.keep else @@ -142,22 +117,13 @@ test_expect_success 'packed obs in alternate ODB kept pack are repacked' ' fi done && git repack -a -d && - myidx=$(ls -1 .git/objects/pack/*.idx) && - test -f "$myidx" && - for p in alt_objects/pack/*.idx; do - git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p" - done | while read sha1 rest; do - if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then - echo "Missing object in local pack: $sha1" - return 1 - fi - done + test_no_missing_in_packs ' test_expect_success 'packed unreachable obs in alternate ODB are not loosened' ' rm -f alt_objects/pack/*.keep && mv .git/objects/pack/* alt_objects/pack/ && - csha1=$(git rev-parse HEAD^{commit}) && + coid=$(git rev-parse HEAD^{commit}) && git reset --hard HEAD^ && test_tick && git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && @@ -167,15 +133,15 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' ' --unpack-unreachable </dev/null pack && rm -f .git/objects/pack/* && mv pack-* .git/objects/pack/ && - test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx | - egrep "^$csha1 " | sort | uniq | wc -l) && - echo > .git/objects/info/alternates && - test_must_fail git show $csha1 + git verify-pack -v -- .git/objects/pack/*.idx >packlist && + ! grep "^$coid " packlist && + echo >.git/objects/info/alternates && + test_must_fail git show $coid ' test_expect_success 'local packed unreachable obs that exist in alternate ODB are not loosened' ' - echo $(pwd)/alt_objects > .git/objects/info/alternates && - echo "$csha1" | git pack-objects --non-empty --all --reflog pack && + echo $(pwd)/alt_objects >.git/objects/info/alternates && + echo "$coid" | git pack-objects --non-empty --all --reflog pack && rm -f .git/objects/pack/* && mv pack-* .git/objects/pack/ && # The pack-objects call on the next line is equivalent to @@ -184,10 +150,10 @@ test_expect_success 'local packed unreachable obs that exist in alternate ODB ar --unpack-unreachable </dev/null pack && rm -f .git/objects/pack/* && mv pack-* .git/objects/pack/ && - test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx | - egrep "^$csha1 " | sort | uniq | wc -l) && - echo > .git/objects/info/alternates && - test_must_fail git show $csha1 + git verify-pack -v -- .git/objects/pack/*.idx >packlist && + ! grep "^$coid " && + echo >.git/objects/info/alternates && + test_must_fail git show $coid ' test_expect_success 'objects made unreachable by grafts only are kept' ' @@ -196,7 +162,7 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' H0=$(git rev-parse HEAD) && H1=$(git rev-parse HEAD^) && H2=$(git rev-parse HEAD^^) && - echo "$H0 $H2" > .git/info/grafts && + echo "$H0 $H2" >.git/info/grafts && git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && git repack -a -d && git cat-file -t $H1 @@ -235,7 +201,7 @@ test_expect_success 'incremental repack does not complain' ' test_expect_success 'bitmaps can be disabled on bare repos' ' git -c repack.writeBitmaps=false -C bare.git repack -ad && - bitmap=$(ls bare.git/objects/pack/*.bitmap 2>/dev/null || :) && + bitmap=$(ls bare.git/objects/pack/*.bitmap || :) && test -z "$bitmap" ' diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh index d1ebfd88c7..a98785da79 100755 --- a/t/t7811-grep-open.sh +++ b/t/t7811-grep-open.sh @@ -113,7 +113,6 @@ test_expect_success 'modified file' ' subdir/grep.c unrelated EOF - >empty && echo "enum grep_pat_token" >unrelated && test_when_finished "git checkout HEAD unrelated" && diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index 531eb59d57..03dba6685a 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -70,15 +70,18 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep ASCII from invalid UT test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data' ' git grep -h "æ" invalid-0x80 >actual && test_cmp expected actual && - git grep -h "(*NO_JIT)æ" invalid-0x80 && + git grep -h "(*NO_JIT)æ" invalid-0x80 >actual && test_cmp expected actual ' test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data with -i' ' test_might_fail git grep -hi "Æ" invalid-0x80 >actual && - test_cmp expected actual && - test_must_fail git grep -hi "(*NO_JIT)Æ" invalid-0x80 && - test_cmp expected actual + if test -s actual + then + test_cmp expected actual + fi && + test_must_fail git grep -hi "(*NO_JIT)Æ" invalid-0x80 >actual && + ! test_cmp expected actual ' test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index bd94779611..a834afab4d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -2066,7 +2066,7 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' ' TO1=$(echo "QTo 1 <to1@example.com>" | q_to_tab) && TO2=$(echo "QZto2" | qz_to_tab_space) && CC1=$(echo "cc1" | append_cr) && - BCC1=$(echo "Q bcc1@example.com Q" | q_to_nul) && + BCC1=$(echo " bcc1@example.com Q" | q_to_nul) && git send-email \ --dry-run \ --from=" Example <from@example.com>" \ diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index 0b20b07e68..c90fdc5c89 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -53,8 +53,6 @@ text_no_props () { printf "%s\n" "$text" } ->empty - test_expect_success 'empty dump' ' reinit_git && echo "SVN-fs-dump-format-version: 2" >input && @@ -208,7 +206,7 @@ test_expect_failure 'timestamp and empty file' ' test_cmp expect.date actual.date && test_cmp expect.files actual.files && git checkout HEAD empty-file && - test_cmp empty file + test_must_be_empty file ' test_expect_success 'directory with files' ' diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index e707fb861e..05f07a1671 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -2143,12 +2143,27 @@ test_expect_success 'R: abort on receiving feature after data command' ' test_must_fail git fast-import <input ' +test_expect_success 'R: import-marks features forbidden by default' ' + >git.marks && + echo "feature import-marks=git.marks" >input && + test_must_fail git fast-import <input && + echo "feature import-marks-if-exists=git.marks" >input && + test_must_fail git fast-import <input +' + test_expect_success 'R: only one import-marks feature allowed per stream' ' + >git.marks && + >git2.marks && cat >input <<-EOF && feature import-marks=git.marks feature import-marks=git2.marks EOF + test_must_fail git fast-import --allow-unsafe-features <input +' + +test_expect_success 'R: export-marks feature forbidden by default' ' + echo "feature export-marks=git.marks" >input && test_must_fail git fast-import <input ' @@ -2162,19 +2177,29 @@ test_expect_success 'R: export-marks feature results in a marks file being creat EOF - cat input | git fast-import && + git fast-import --allow-unsafe-features <input && grep :1 git.marks ' test_expect_success 'R: export-marks options can be overridden by commandline options' ' - cat input | git fast-import --export-marks=other.marks && - grep :1 other.marks + cat >input <<-\EOF && + feature export-marks=feature-sub/git.marks + blob + mark :1 + data 3 + hi + + EOF + git fast-import --allow-unsafe-features \ + --export-marks=cmdline-sub/other.marks <input && + grep :1 cmdline-sub/other.marks && + test_path_is_missing feature-sub ' test_expect_success 'R: catch typo in marks file name' ' test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null && echo "feature import-marks=nonexistent.marks" | - test_must_fail git fast-import + test_must_fail git fast-import --allow-unsafe-features ' test_expect_success 'R: import and output marks can be the same file' ' @@ -2229,7 +2254,8 @@ test_expect_success 'R: --import-marks-if-exists' ' test_expect_success 'R: feature import-marks-if-exists' ' rm -f io.marks && - git fast-import --export-marks=io.marks <<-\EOF && + git fast-import --export-marks=io.marks \ + --allow-unsafe-features <<-\EOF && feature import-marks-if-exists=not_io.marks EOF test_must_be_empty io.marks && @@ -2240,7 +2266,8 @@ test_expect_success 'R: feature import-marks-if-exists' ' echo ":1 $blob" >expect && echo ":2 $blob" >>expect && - git fast-import --export-marks=io.marks <<-\EOF && + git fast-import --export-marks=io.marks \ + --allow-unsafe-features <<-\EOF && feature import-marks-if-exists=io.marks blob mark :2 @@ -2253,7 +2280,8 @@ test_expect_success 'R: feature import-marks-if-exists' ' echo ":3 $blob" >>expect && git fast-import --import-marks=io.marks \ - --export-marks=io.marks <<-\EOF && + --export-marks=io.marks \ + --allow-unsafe-features <<-\EOF && feature import-marks-if-exists=not_io.marks blob mark :3 @@ -2264,7 +2292,8 @@ test_expect_success 'R: feature import-marks-if-exists' ' test_cmp expect io.marks && git fast-import --import-marks-if-exists=not_io.marks \ - --export-marks=io.marks <<-\EOF && + --export-marks=io.marks \ + --allow-unsafe-features <<-\EOF && feature import-marks-if-exists=io.marks EOF test_must_be_empty io.marks @@ -2276,7 +2305,7 @@ test_expect_success 'R: import to output marks works without any content' ' feature export-marks=marks.new EOF - cat input | git fast-import && + git fast-import --allow-unsafe-features <input && test_cmp marks.out marks.new ' @@ -2286,7 +2315,7 @@ test_expect_success 'R: import marks prefers commandline marks file over the str feature export-marks=marks.new EOF - cat input | git fast-import --import-marks=marks.out && + git fast-import --import-marks=marks.out --allow-unsafe-features <input && test_cmp marks.out marks.new ' @@ -2299,7 +2328,8 @@ test_expect_success 'R: multiple --import-marks= should be honoured' ' head -n2 marks.out > one.marks && tail -n +3 marks.out > two.marks && - git fast-import --import-marks=one.marks --import-marks=two.marks <input && + git fast-import --import-marks=one.marks --import-marks=two.marks \ + --allow-unsafe-features <input && test_cmp marks.out combined.marks ' @@ -2312,7 +2342,7 @@ test_expect_success 'R: feature relative-marks should be honoured' ' mkdir -p .git/info/fast-import/ && cp marks.new .git/info/fast-import/relative.in && - git fast-import <input && + git fast-import --allow-unsafe-features <input && test_cmp marks.new .git/info/fast-import/relative.out ' @@ -2324,7 +2354,7 @@ test_expect_success 'R: feature no-relative-marks should be honoured' ' feature export-marks=non-relative.out EOF - git fast-import <input && + git fast-import --allow-unsafe-features <input && test_cmp marks.new non-relative.out ' @@ -2477,9 +2507,6 @@ test_expect_success PIPE 'R: copy using cat-file' ' echo $expect_id blob $expect_len >expect.response && rm -f blobs && - cat >frontend <<-\FRONTEND_END && - #!/bin/sh - FRONTEND_END mkfifo blobs && ( @@ -2594,7 +2621,7 @@ test_expect_success 'R: quiet option results in no stats being output' ' EOF - cat input | git fast-import 2> output && + git fast-import 2>output <input && test_must_be_empty output ' diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index db084fe625..690c90fb82 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -600,9 +600,10 @@ test_expect_success 'directory becomes symlink' ' test_expect_success 'fast-export quotes pathnames' ' git init crazy-paths && + test_config -C crazy-paths core.protectNTFS false && (cd crazy-paths && blob=$(echo foo | git hash-object -w --stdin) && - git update-index --add \ + git -c core.protectNTFS=false update-index --add \ --cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \ --cacheinfo 100644 $blob "path with \"quote\"" \ --cacheinfo 100644 $blob "path with \\backslash" \ diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index b299ecc326..284c52d076 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -308,7 +308,7 @@ test_commit_bulk () { total=$1 add_from= - if git -C "$indir" rev-parse --verify "$ref" + if git -C "$indir" rev-parse --quiet --verify "$ref" then add_from=t fi @@ -1012,19 +1012,30 @@ test_must_be_empty () { fi } -# Tests that its two parameters refer to the same revision +# Tests that its two parameters refer to the same revision, or if '!' is +# provided first, that its other two parameters refer to different +# revisions. test_cmp_rev () { + local op='=' wrong_result=different + + if test $# -ge 1 && test "x$1" = 'x!' + then + op='!=' + wrong_result='the same' + shift + fi if test $# != 2 then error "bug in the test script: test_cmp_rev requires two revisions, but got $#" else local r1 r2 r1=$(git rev-parse --verify "$1") && - r2=$(git rev-parse --verify "$2") && - if test "$r1" != "$r2" + r2=$(git rev-parse --verify "$2") || return 1 + + if ! test "$r1" "$op" "$r2" then cat >&4 <<-EOF - error: two revisions point to different objects: + error: two revisions point to $wrong_result objects: '$1': $r1 '$2': $r2 EOF @@ -1175,6 +1186,34 @@ perl () { command "$PERL_PATH" "$@" 2>&7 } 7>&2 2>&4 +# Given the name of an environment variable with a bool value, normalize +# its value to a 0 (true) or 1 (false or empty string) return code. +# +# test_bool_env GIT_TEST_HTTPD <default-value> +# +# Return with code corresponding to the given default value if the variable +# is unset. +# Abort the test script if either the value of the variable or the default +# are not valid bool values. + +test_bool_env () { + if test $# != 2 + then + BUG "test_bool_env requires two parameters (variable name and default value)" + fi + + git env--helper --type=bool --default="$2" --exit-code "$1" + ret=$? + case $ret in + 0|1) # unset or valid bool value + ;; + *) # invalid bool value or something unexpected + error >&7 "test_bool_env requires bool values both for \$$1 and for the default fallback" + ;; + esac + return $ret +} + # Exit the test suite, either by skipping all remaining tests or by # exiting with an error. If our prerequisite variable $1 falls back # on a default assume we were opportunistically trying to set up some @@ -1183,7 +1222,7 @@ perl () { # The error/skip message should be given by $2. # test_skip_or_die () { - if ! git env--helper --type=bool --default=false --exit-code $1 + if ! test_bool_env "$1" false then skip_all=$2 test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index e8ae150d75..44df51be8f 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1406,23 +1406,23 @@ yes () { # The GIT_TEST_FAIL_PREREQS code hooks into test_set_prereq(), and # thus needs to be set up really early, and set an internal variable # for convenience so the hot test_set_prereq() codepath doesn't need -# to call "git env--helper". Only do that work if needed by seeing if -# GIT_TEST_FAIL_PREREQS is set at all. +# to call "git env--helper" (via test_bool_env). Only do that work +# if needed by seeing if GIT_TEST_FAIL_PREREQS is set at all. GIT_TEST_FAIL_PREREQS_INTERNAL= if test -n "$GIT_TEST_FAIL_PREREQS" then - if git env--helper --type=bool --default=0 --exit-code GIT_TEST_FAIL_PREREQS + if test_bool_env GIT_TEST_FAIL_PREREQS false then GIT_TEST_FAIL_PREREQS_INTERNAL=true test_set_prereq FAIL_PREREQS fi else test_lazy_prereq FAIL_PREREQS ' - git env--helper --type=bool --default=0 --exit-code GIT_TEST_FAIL_PREREQS + test_bool_env GIT_TEST_FAIL_PREREQS false ' fi -# Fix some commands on Windows +# Fix some commands on Windows, and other OS-specific things uname_s=$(uname -s) case $uname_s in *MINGW*) @@ -1453,6 +1453,12 @@ case $uname_s in test_set_prereq SED_STRIPS_CR test_set_prereq GREP_STRIPS_CR ;; +FreeBSD) + test_set_prereq REGEX_ILLSEQ + test_set_prereq POSIXPERM + test_set_prereq BSLASHPSPEC + test_set_prereq EXECKEEPSPID + ;; *) test_set_prereq POSIXPERM test_set_prereq BSLASHPSPEC @@ -1477,7 +1483,7 @@ then fi test_lazy_prereq C_LOCALE_OUTPUT ' - ! git env--helper --type=bool --default=0 --exit-code GIT_TEST_GETTEXT_POISON + ! test_bool_env GIT_TEST_GETTEXT_POISON false ' if test -z "$GIT_TEST_CHECK_CACHE_TREE" @@ -1,3 +1,6 @@ +#ifndef TAR_H +#define TAR_H + #define TYPEFLAG_AUTO '\0' #define TYPEFLAG_REG '0' #define TYPEFLAG_LNK '2' @@ -23,3 +26,5 @@ struct ustar_header { char devminor[8]; /* 337 */ char prefix[155]; /* 345 */ }; + +#endif /* TAR_H */ diff --git a/transport-helper.c b/transport-helper.c index a9d690297e..413d9d873e 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -435,6 +435,7 @@ static int get_importer(struct transport *transport, struct child_process *fasti child_process_init(fastimport); fastimport->in = xdup(helper->out); argv_array_push(&fastimport->args, "fast-import"); + argv_array_push(&fastimport->args, "--allow-unsafe-features"); argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet"); if (data->bidi_import) { diff --git a/tree-walk.c b/tree-walk.c index bea819d826..b3d162051f 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -43,6 +43,12 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l strbuf_addstr(err, _("empty filename in tree entry")); return -1; } +#ifdef GIT_WINDOWS_NATIVE + if (protect_ntfs && strchr(path, '\\')) { + strbuf_addf(err, _("filename in tree entry contains backslash: '%s'"), path); + return -1; + } +#endif len = strlen(path) + 1; /* Initialize the descriptor entry */ @@ -1124,7 +1130,7 @@ match_wildcards: * later on. * max_depth is ignored but we may consider support it * in future, see - * https://public-inbox.org/git/7vmxo5l2g4.fsf@alter.siamese.dyndns.org/ + * https://lore.kernel.org/git/7vmxo5l2g4.fsf@alter.siamese.dyndns.org/ */ if (ps->recursive && S_ISDIR(entry->mode)) return entry_interesting; diff --git a/unpack-trees.c b/unpack-trees.c index 28cbd19570..a75b068d33 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1504,6 +1504,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->merge_size = len; mark_all_ce_unused(o->src_index); + if (o->src_index->fsmonitor_last_update) + o->result.fsmonitor_last_update = o->src_index->fsmonitor_last_update; + /* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries */ @@ -2060,7 +2063,8 @@ static int merged_entry(const struct cache_entry *ce, invalidate_ce_path(old, o); } - do_add_entry(o, merge, update, CE_STAGEMASK); + if (do_add_entry(o, merge, update, CE_STAGEMASK) < 0) + return -1; return 1; } @@ -2384,7 +2388,8 @@ int oneway_merge(const struct cache_entry * const *src, if (old && same(old, a)) { int update = 0; - if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old)) { + if (o->reset && o->update && !ce_uptodate(old) && !ce_skip_worktree(old) && + !(old->ce_flags & CE_FSMONITOR_VALID)) { struct stat st; if (lstat(old->name, &st) || ie_match_stat(o->src_index, old, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE)) diff --git a/userdiff.c b/userdiff.c index 577053c10a..324916f20f 100644 --- a/userdiff.c +++ b/userdiff.c @@ -145,7 +145,7 @@ PATTERNS("php", "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"), -PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", +PATTERNS("python", "^[ \t]*((class|(async[ \t]+)?def)[ \t].*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" |
