diff options
275 files changed, 8115 insertions, 1801 deletions
diff --git a/.travis.yml b/.travis.yml index 281f101f31..4684b3f4f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,30 +21,14 @@ addons: - git-svn - apache2 -env: - global: - - DEVELOPER=1 - # The Linux build installs the defined dependency versions below. - # The OS X build installs the latest available versions. Keep that - # in mind when you encounter a broken OS X build! - - LINUX_P4_VERSION="16.2" - - LINUX_GIT_LFS_VERSION="1.5.2" - - DEFAULT_TEST_TARGET=prove - - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - - GIT_TEST_OPTS="--verbose-log" - - GIT_TEST_CLONE_2GB=YesPlease - # t9810 occasionally fails on Travis CI OS X - # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X - - GIT_SKIP_TESTS="t9810 t9816" - matrix: include: - - env: GETTEXT_POISON=YesPlease + - env: jobname=GETTEXT_POISON os: linux compiler: addons: before_install: - - env: Windows + - env: jobname=Windows os: linux compiler: addons: @@ -55,15 +39,16 @@ matrix: test "$TRAVIS_REPO_SLUG" != "git/git" || ci/run-windows-build.sh $TRAVIS_BRANCH $(git rev-parse HEAD) after_failure: - - env: Linux32 + - env: jobname=Linux32 os: linux compiler: + addons: services: - docker before_install: before_script: script: ci/run-linux32-docker.sh - - env: Static Analysis + - env: jobname=StaticAnalysis os: linux compiler: addons: @@ -74,7 +59,7 @@ matrix: before_script: script: ci/run-static-analysis.sh after_failure: - - env: Documentation + - env: jobname=Documentation os: linux compiler: addons: diff --git a/Documentation/Makefile b/Documentation/Makefile index 2ab65561af..4ae9ba5c86 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -39,6 +39,7 @@ MAN7_TXT += gitworkflows.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT)) MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) +GIT_MAN_REF = master OBSOLETE_HTML += everyday.html OBSOLETE_HTML += git-remote-helpers.html @@ -437,14 +438,14 @@ require-manrepo:: then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi quick-install-man: require-manrepo - '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) $(GIT_MAN_REF) require-htmlrepo:: @if test ! -d $(HTML_REPO); \ then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi quick-install-html: require-htmlrepo - '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) $(GIT_MAN_REF) print-man1: @for i in $(MAN1_TXT); do echo $$i; done diff --git a/Documentation/RelNotes/2.15.1.txt b/Documentation/RelNotes/2.15.1.txt index 47f23b56fe..ec06704e63 100644 --- a/Documentation/RelNotes/2.15.1.txt +++ b/Documentation/RelNotes/2.15.1.txt @@ -13,7 +13,7 @@ Fixes since v2.15 latter, which has been fixed. * The experimental "color moved lines differently in diff output" - feature was buggy around "ignore whitespace changes" edges, whihch + feature was buggy around "ignore whitespace changes" edges, which has been corrected. * Instead of using custom line comparison and hashing functions to @@ -24,7 +24,7 @@ Fixes since v2.15 HEAD points at, which have been fixed. * "git commit", after making a commit, did not check for errors when - asking on what branch it made the commit, which has been correted. + asking on what branch it made the commit, which has been corrected. * "git status --ignored -u" did not stop at a working tree of a separate project that is embedded in an ignored directory and @@ -35,7 +35,7 @@ Fixes since v2.15 --recurse-submodules" has been fixed. * A recent regression in "git rebase -i" that broke execution of git - commands from subdirectories via "exec" insn has been fixed. + commands from subdirectories via "exec" instruction has been fixed. * "git check-ref-format --branch @{-1}" bit a "BUG()" when run outside a repository for obvious reasons; clarify the documentation @@ -76,5 +76,13 @@ Fixes since v2.15 * We used to add an empty alternate object database to the system that does not help anything; it has been corrected. + * Error checking in "git imap-send" for empty response has been + improved. + + * An ancient bug in "git apply --ignore-space-change" codepath has + been fixed. + + * There was a recent semantic mismerge in the codepath to write out a + section of a configuration section, which has been corrected. Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.15.2.txt b/Documentation/RelNotes/2.15.2.txt new file mode 100644 index 0000000000..9f7e28f8a2 --- /dev/null +++ b/Documentation/RelNotes/2.15.2.txt @@ -0,0 +1,47 @@ +Git v2.15.2 Release Notes +========================= + +Fixes since v2.15.1 +------------------- + + * Recent update to the refs infrastructure implementation started + rewriting packed-refs file more often than before; this has been + optimized again for most trivial cases. + + * The SubmittingPatches document has been converted to produce an + HTML version via AsciiDoc/Asciidoctor. + + * Contrary to the documentation, "git pull -4/-6 other-args" did not + ask the underlying "git fetch" to go over IPv4/IPv6, which has been + corrected. + + * When "git rebase" prepared an mailbox of changes and fed it to "git + am" to replay them, it was confused when a stray "From " happened + to be in the log message of one of the replayed changes. This has + been corrected. + + * Command line completion (in contrib/) has been taught about the + "--copy" option of "git branch". + + * "git apply --inaccurate-eof" when used with "--ignore-space-change" + triggered an internal sanity check, which has been fixed. + + * The sequencer machinery (used by "git cherry-pick A..B", and "git + rebase -i", among other things) would have lost a commit if stopped + due to an unlockable index file, which has been fixed. + + * The three-way merge performed by "git cherry-pick" was confused + when a new submodule was added in the meantime, which has been + fixed (or "papered over"). + + * "git notes" sent its error message to its standard output stream, + which was corrected. + + * A few scripts (both in production and tests) incorrectly redirected + their error output. These have been corrected. + + * Clarify and enhance documentation for "merge-base --fork-point", as + it was clear what it computed but not why/what for. + + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.16.0.txt b/Documentation/RelNotes/2.16.0.txt index f57acec735..0c8464492e 100644 --- a/Documentation/RelNotes/2.16.0.txt +++ b/Documentation/RelNotes/2.16.0.txt @@ -61,11 +61,90 @@ UI, Workflows & Features * The SubmittingPatches document has been converted to produce an HTML version via AsciiDoc/Asciidoctor. - (merge 049e64aa50 bc/submitting-patches-in-asciidoc later to maint). * We learned to talk to watchman to speed up "git status" and other operations that need to see which paths have been modified. + * The "diff" family of commands learned to ignore differences in + carriage return at the end of line. + + * Places that know about "sendemail.to", like documentation and shell + completion (in contrib/) have been taught about "sendemail.tocmd", + too. + + * "git add --renormalize ." is a new and safer way to record the fact + that you are correcting the end-of-line convention and other + "convert_to_git()" glitches in the in-repository data. + + * "git branch" and "git checkout -b" are now forbidden from creating + a branch whose name is "HEAD". + + * "git branch --list" learned to show its output through the pager by + default when the output is going to a terminal, which is controlled + by the pager.branch configuration variable. This is similar to a + recent change to "git tag --list". + + * "git grep -W", "git diff -W" and their friends learned a heuristic + to extend a pre-context beyond the line that matches the "function + pattern" (aka "diff.*.xfuncname") to include a comment block, if + exists, that immediately precedes it. + + * "git config --expiry-date gc.reflogexpire" can read "2.weeks" from + the configuration and report it as a timestamp, just like "--int" + would read "1k" and report 1024, to help consumption by scripts. + + * The shell completion (in contrib/) learned that "git pull" can take + the "--autostash" option. + + * The tagnames "git log --decorate" uses to annotate the commits can + now be limited to subset of available refs with the two additional + options, --decorate-refs[-exclude]=<pattern>. + + * "git grep" compiled with libpcre2 sometimes triggered a segfault, + which is being fixed. + + * "git send-email" tries to see if the sendmail program is available + in /usr/lib and /usr/sbin; extend the list of locations to be + checked to also include directories on $PATH. + + * "git diff" learned, "--anchored", a variant of the "--patience" + algorithm, to which the user can specify which 'unique' line to be + used as anchoring points. + + * The way "git worktree add" determines what branch to create from + where and checkout in the new worktree has been updated a bit. + + * Ancient part of codebase still shows dots after an abbreviated + object name just to show that it is not a full object name, but + these ellipses are confusing to people who newly discovered Git + who are used to seeing abbreviated object names and find them + confusing with the range syntax. + + * With a configuration variable rebase.abbreviateCommands set, + "git rebase -i" produces the todo list with a single-letter + command names. + + * "git worktree add" learned to run the post-checkout hook, just like + "git checkout" does, after the initial checkout. + + * "git svn" has been updated to strip CRs in the commit messages, as + recent versions of Subversion rejects them. + + * "git imap-send" did not correctly quote the folder name when + making a request to the server, which has been corrected. + + * Error messages from "git rebase" have been somewhat cleaned up. + + * Git has been taught to support an https:// URL used for http.proxy + when using recent versions of libcurl. + + * "git merge" learned to pay attention to merge.verifySignatures + configuration variable and pretend as if '--verify-signatures' + option was given from the command line. + + * "git describe" was taught to dig trees deeper to find a + <commit-ish>:<path> that refers to a given blob object. + Performance, Internal Implementation, Development Support etc. @@ -106,6 +185,46 @@ Performance, Internal Implementation, Development Support etc. * Drop (perhaps overly cautious) sanity check before using the index read from the filesystem at runtime. + * The build procedure has been taught to avoid some unnecessary + instability in the build products. + + * A new mechanism to upgrade the wire protocol in place is proposed + and demonstrated that it works with the older versions of Git + without harming them. + + * An infrastructure to define what hash function is used in Git is + introduced, and an effort to plumb that throughout various + codepaths has been started. + + * The code to iterate over loose object files got optimized. + + * An internal function that was left for backward compatibility has + been removed, as there is no remaining callers. + + * Historically, the diff machinery for rename detection had a + hardcoded limit of 32k paths; this is being lifted to allow users + trade cycles with a (possibly) easier to read result. + + * The tracing infrastructure has been optimized for cases where no + tracing is requested. + + * In preparation for implementing narrow/partial clone, the object + walking machinery has been taught a way to tell it to "filter" some + objects from enumeration. + + * A few structures and variables that are implementation details of + the decorate API have been renamed and then the API got documented + better. + + * Assorted updates for TravisCI integration. + (merge 4f26366679 sg/travis-fixes later to maint). + + * Introduce a helper to simplify code to parse a common pattern that + expects either "--key" or "--key=<something>". + + * "git version --build-options" learned to report the host CPU and + the exact commit object name the binary was built from. + Also contains various documentation updates and code clean-ups. @@ -119,7 +238,7 @@ Fixes since v2.15 latter, which has been fixed. * The experimental "color moved lines differently in diff output" - feature was buggy around "ignore whitespace changes" edges, whihch + feature was buggy around "ignore whitespace changes" edges, which has been corrected. * Instead of using custom line comparison and hashing functions to @@ -130,7 +249,7 @@ Fixes since v2.15 HEAD points at, which have been fixed. * "git commit", after making a commit, did not check for errors when - asking on what branch it made the commit, which has been correted. + asking on what branch it made the commit, which has been corrected. * "git status --ignored -u" did not stop at a working tree of a separate project that is embedded in an ignored directory and @@ -141,7 +260,7 @@ Fixes since v2.15 --recurse-submodules" has been fixed. * A recent regression in "git rebase -i" that broke execution of git - commands from subdirectories via "exec" insn has been fixed. + commands from subdirectories via "exec" instruction has been fixed. * A (possibly flakey) test fix. @@ -178,12 +297,10 @@ Fixes since v2.15 * Error checking in "git imap-send" for empty response has been improved. - (merge 618ec81abb rs/imap-send-next-arg-fix later to maint). * Recent update to the refs infrastructure implementation started rewriting packed-refs file more often than before; this has been optimized again for most trivial cases. - (merge 7c6bd25c7d mh/avoid-rewriting-packed-refs later to maint). * Some error messages did not quote filenames shown in it, which have been fixed. @@ -198,11 +315,165 @@ Fixes since v2.15 that does not help anything; it has been corrected. * Doc update around use of "format-patch --subject-prefix" etc. - (merge f6be7edcac ad/submitting-patches-title-decoration later to maint). * A fix for an ancient bug in "git apply --ignore-space-change" codepath. - (merge 6ce15ce576 rs/apply-fuzzy-match-fix later to maint). + + * Clarify and enhance documentation for "merge-base --fork-point", as + it was clear what it computed but not why/what for. + + * A few scripts (both in production and tests) incorrectly redirected + their error output. These have been corrected. + + * "git notes" sent its error message to its standard output stream, + which was corrected. + + * The three-way merge performed by "git cherry-pick" was confused + when a new submodule was added in the meantime, which has been + fixed (or "papered over"). + + * The sequencer machinery (used by "git cherry-pick A..B", and "git + rebase -i", among other things) would have lost a commit if stopped + due to an unlockable index file, which has been fixed. + + * "git apply --inaccurate-eof" when used with "--ignore-space-change" + triggered an internal sanity check, which has been fixed. + + * Command line completion (in contrib/) has been taught about the + "--copy" option of "git branch". + + * When "git rebase" prepared an mailbox of changes and fed it to "git + am" to replay them, it was confused when a stray "From " happened + to be in the log message of one of the replayed changes. This has + been corrected. + + * There was a recent semantic mismerge in the codepath to write out a + section of a configuration section, which has been corrected. + + * Mentions of "git-rebase" and "git-am" (dashed form) still remained + in end-user visible strings emitted by the "git rebase" command; + they have been corrected. + + * Contrary to the documentation, "git pull -4/-6 other-args" did not + ask the underlying "git fetch" to go over IPv4/IPv6, which has been + corrected. + + * "git checkout --recursive" may overwrite and rewind the history of + the branch that happens to be checked out in submodule + repositories, which might not be desirable. Detach the HEAD but + still allow the recursive checkout to succeed in such a case. + (merge 57f22bf997 sb/submodule-recursive-checkout-detach-head later to maint). + + * "git branch --set-upstream" has been deprecated and (sort of) + removed, as "--set-upstream-to" is the preferred one these days. + The documentation still had "--set-upstream" listed on its + synopsis section, which has been corrected. + (merge a060f3d3d8 tz/branch-doc-remove-set-upstream later to maint). + + * Internally we use 0{40} as a placeholder object name to signal the + codepath that there is no such object (e.g. the fast-forward check + while "git fetch" stores a new remote-tracking ref says "we know + there is no 'old' thing pointed at by the ref, as we are creating + it anew" by passing 0{40} for the 'old' side), and expect that a + codepath to locate an in-core object to return NULL as a sign that + the object does not exist. A look-up for an object that does not + exist however is quite costly with a repository with large number + of packfiles. This access pattern has been optimized. + (merge 87b5e236a1 jk/fewer-pack-rescan later to maint). + + * In addition to "git stash -m message", the command learned to + accept "git stash -mmessage" form. + (merge 5675473fcb ph/stash-save-m-option-fix later to maint). + + * @{-N} in "git checkout @{-N}" may refer to a detached HEAD state, + but the documentation was not clear about it, which has been fixed. + (merge 75ce149575 ks/doc-checkout-previous later to maint). + + * A regression in the progress eye-candy was fixed. + (merge 9c5951cacf jk/progress-delay-fix later to maint). + + * The code internal to the recursive merge strategy was not fully + prepared to see a path that is renamed to try overwriting another + path that is only different in case on case insensitive systems. + This does not matter in the current code, but will start to matter + once the rename detection logic starts taking hints from nearby + paths moving to some directory and moves a new path along with them. + (merge 4cba2b0108 en/merge-recursive-icase-removal later to maint). + + * An v2.12-era regression in pathspec match logic, which made it look + into submodule tree even when it is not desired, has been fixed. + (merge eef3df5a93 bw/pathspec-match-submodule-boundary later to maint). + + * Amending commits in git-gui broke the author name that is non-ascii + due to incorrect enconding conversion. + + * Recent update to the submodule configuration code broke "diff-tree" + by accidentally stopping to read from the index upfront. + (merge fd66bcc31f bw/submodule-config-cleanup later to maint). + + * Git shows a message to tell the user that it is waiting for the + user to finish editing when spawning an editor, in case the editor + opens to a hidden window or somewhere obscure and the user gets + lost. + (merge abfb04d0c7 ls/editor-waiting-message later to maint). + + * The "safe crlf" check incorrectly triggered for contents that does + not use CRLF as line endings, which has been corrected. + (merge 649f1f0948 tb/check-crlf-for-safe-crlf later to maint). + + * "git clone --shared" to borrow from a (secondary) worktree did not + work, even though "git clone --local" did. Both are now accepted. + (merge b3b05971c1 es/clone-shared-worktree later to maint). + + * The build procedure now allows not just the repositories but also + the refs to be used to take pre-formatted manpages and html + documents to install. + (merge 65289e9dcd rb/quick-install-doc later to maint). + + * Update the shell prompt script (in contrib/) to strip trailing CR + from strings read from various "state" files. + (merge 041fe8fc83 ra/prompt-eread-fix later to maint). + + * "git merge -s recursive" did not correctly abort when the index is + dirty, if the merged tree happened to be the same as the current + HEAD, which has been fixed. + + * Bytes with high-bit set were encoded incorrectly and made + credential helper fail. + (merge 4c267f2ae3 jd/fix-strbuf-add-urlencode-bytes later to maint). + + * "git rebase -p -X<option>" did not propagate the option properly + down to underlying merge strategy backend. + (merge dd6fb0053c js/fix-merge-arg-quoting-in-rebase-p later to maint). + + * "git merge -s recursive" did not correctly abort when the index is + dirty, if the merged tree happened to be the same as the current + HEAD, which has been fixed. + (merge f309e8e768 ew/empty-merge-with-dirty-index-maint later to maint). * Other minor doc, test and build updates and code cleanups. - (merge f4e45cb3eb ma/bisect-leakfix later to maint). - (merge 4da72644b7 ma/reduce-heads-leakfix later to maint). + (merge 1a1fc2d5b5 rd/man-prune-progress later to maint). + (merge 0ba014035a rd/man-reflog-add-n later to maint). + (merge e54b63359f rd/doc-notes-prune-fix later to maint). + (merge ff4c9b413a sp/doc-info-attributes later to maint). + (merge 7db2cbf4f1 jc/receive-pack-hook-doc later to maint). + (merge 5a0526264b tg/t-readme-updates later to maint). + (merge 5e83cca0b8 jk/no-optional-locks later to maint). + (merge 826c778f7c js/hashmap-update-sample later to maint). + (merge 176b2d328c sg/setup-doc-update later to maint). + (merge 1b09073514 rs/am-builtin-leakfix later to maint). + (merge addcf6cfde rs/fmt-merge-msg-string-leak-fix later to maint). + (merge c3ff8f6c14 rs/strbuf-read-once-reset-length later to maint). + (merge 6b0eb884f9 db/doc-workflows-neuter-the-maintainer later to maint). + (merge 8c87bdfb21 jk/cvsimport-quoting later to maint). + (merge 176cb979fe rs/fmt-merge-msg-leakfix later to maint). + (merge 5a03360e73 tb/delimit-pretty-trailers-args-with-comma later to maint). + (merge d0e6326026 ot/pretty later to maint). + (merge 44103f4197 sb/test-helper-excludes later to maint). + (merge 170078693f jt/transport-no-more-rsync later to maint). + (merge c07b3adff1 bw/path-doc later to maint). + (merge bf9d7df950 tz/lib-git-svn-svnserve-tests later to maint). + (merge dec366c9a8 sr/http-sslverify-config-doc later to maint). + (merge 3f824e91c8 jk/test-suite-tracing later to maint). + (merge 1feb061701 db/doc-config-section-names-with-bs later to maint). + (merge 74dea0e13c jh/memihash-opt later to maint). + (merge 2e9fdc795c ma/bisect-leakfix later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 3ef30922ec..a1d0feca36 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -261,7 +261,7 @@ not a text/plain, it's something else. Send your patch with "To:" set to the mailing list, with "cc:" listing people who are involved in the area you are touching (the output from -+git blame _$path_+ and +git shortlog {litdd}no-merges _$path_+ would help to +`git blame $path` and `git shortlog --no-merges $path` would help to identify them), to solicit comments and reviews. :1: footnote:[The current maintainer: gitster@pobox.com] diff --git a/Documentation/config.txt b/Documentation/config.txt index 4966d90ebb..0e25b2c92b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -41,11 +41,13 @@ in the section header, like in the example below: -------- Subsection names are case sensitive and can contain any characters except -newline (doublequote `"` and backslash can be included by escaping them -as `\"` and `\\`, respectively). Section headers cannot span multiple -lines. Variables may belong directly to a section or to a given subsection. -You can have `[section]` if you have `[section "subsection"]`, but you -don't need to. +newline and the null byte. Doublequote `"` and backslash can be included +by escaping them as `\"` and `\\`, respectively. Backslashes preceding +other characters are dropped when reading; for example, `\t` is read as +`t` and `\0` is read as `0` Section headers cannot span multiple lines. +Variables may belong directly to a section or to a given subsection. You +can have `[section]` if you have `[section "subsection"]`, but you don't +need to. There is also a deprecated `[section.subsection]` syntax. With this syntax, the subsection name is converted to lower-case and is also @@ -354,6 +356,9 @@ advice.*:: ignoredHook:: Advice shown if an hook is ignored because the hook is not set as executable. + waitingForEditor:: + Print a message to the terminal whenever Git is waiting for + editor input from the user. -- core.fileMode:: @@ -1965,8 +1970,8 @@ empty string. http.sslVerify:: Whether to verify the SSL certificate when fetching or pushing - over HTTPS. Can be overridden by the `GIT_SSL_NO_VERIFY` environment - variable. + over HTTPS. Defaults to true. Can be overridden by the + `GIT_SSL_NO_VERIFY` environment variable. http.sslCert:: File containing the SSL certificate when fetching or pushing @@ -2108,15 +2113,40 @@ matched against are those given directly to Git commands. This means any URLs visited as a result of a redirection do not participate in matching. ssh.variant:: - Depending on the value of the environment variables `GIT_SSH` or - `GIT_SSH_COMMAND`, or the config setting `core.sshCommand`, Git - auto-detects whether to adjust its command-line parameters for use - with plink or tortoiseplink, as opposed to the default (OpenSSH). + By default, Git determines the command line arguments to use + based on the basename of the configured SSH command (configured + using the environment variable `GIT_SSH` or `GIT_SSH_COMMAND` or + the config setting `core.sshCommand`). If the basename is + unrecognized, Git will attempt to detect support of OpenSSH + options by first invoking the configured SSH command with the + `-G` (print configuration) option and will subsequently use + OpenSSH options (if that is successful) or no options besides + the host and remote command (if it fails). ++ +The config variable `ssh.variant` can be set to override this detection. +Valid values are `ssh` (to use OpenSSH options), `plink`, `putty`, +`tortoiseplink`, `simple` (no options except the host and remote command). +The default auto-detection can be explicitly requested using the value +`auto`. Any other value is treated as `ssh`. This setting can also be +overridden via the environment variable `GIT_SSH_VARIANT`. ++ +The current command-line parameters used for each variant are as +follows: ++ +-- + +* `ssh` - [-p port] [-4] [-6] [-o option] [username@]host command + +* `simple` - [username@]host command + +* `plink` or `putty` - [-P port] [-4] [-6] [username@]host command + +* `tortoiseplink` - [-P port] [-4] [-6] -batch [username@]host command + +-- + -The config variable `ssh.variant` can be set to override this auto-detection; -valid values are `ssh`, `plink`, `putty` or `tortoiseplink`. Any other value -will be treated as normal ssh. This setting can be overridden via the -environment variable `GIT_SSH_VARIANT`. +Except for the `simple` variant, command-line parameters are likely to +change as git gains new features. i18n.commitEncoding:: Character encoding the commit messages are stored in; Git itself @@ -2544,6 +2574,23 @@ The protocol names currently used by git are: `hg` to allow the `git-remote-hg` helper) -- +protocol.version:: + Experimental. If set, clients will attempt to communicate with a + server using the specified protocol version. If unset, no + attempt will be made by the client to communicate using a + particular protocol version, this results in protocol version 0 + being used. + Supported versions: ++ +-- + +* `0` - the original wire protocol. + +* `1` - the original wire protocol with the addition of a version string + in the initial response from the server. + +-- + pull.ff:: By default, Git does not create an extra merge commit when merging a commit that is a descendant of the current commit. Instead, the @@ -2691,36 +2738,7 @@ push.recurseSubmodules:: is retained. You may override this configuration at time of push by specifying '--recurse-submodules=check|on-demand|no'. -rebase.stat:: - Whether to show a diffstat of what changed upstream since the last - rebase. False by default. - -rebase.autoSquash:: - If set to true enable `--autosquash` option by default. - -rebase.autoStash:: - When set to true, automatically create a temporary stash entry - before the operation begins, and apply it after the operation - ends. This means that you can run rebase on a dirty worktree. - However, use with care: the final stash application after a - successful rebase might result in non-trivial conflicts. - Defaults to false. - -rebase.missingCommitsCheck:: - If set to "warn", git rebase -i will print a warning if some - commits are removed (e.g. a line was deleted), however the - rebase will still proceed. If set to "error", it will print - the previous warning and stop the rebase, 'git rebase - --edit-todo' can then be used to correct the error. If set to - "ignore", no checking is done. - To drop a commit without warning or error, use the `drop` - command in the todo-list. - Defaults to "ignore". - -rebase.instructionFormat:: - A format string, as specified in linkgit:git-log[1], to be used for - the instruction list during an interactive rebase. The format will automatically - have the long commit hash prepended to the format. +include::rebase-config.txt[] receive.advertiseAtomic:: By default, git-receive-pack will advertise the atomic push @@ -3007,6 +3025,7 @@ sendemail.smtpPass:: sendemail.suppresscc:: sendemail.suppressFrom:: sendemail.to:: +sendemail.tocmd:: sendemail.smtpDomain:: sendemail.smtpServer:: sendemail.smtpServerPort:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index dd0dba5b1d..743af97b06 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -80,6 +80,16 @@ endif::git-format-patch[] --histogram:: Generate a diff using the "histogram diff" algorithm. +--anchored=<text>:: + Generate a diff using the "anchored diff" algorithm. ++ +This option may be specified more than once. ++ +If a line exists in both the source and destination, exists only once, +and starts with this text, this algorithm attempts to prevent it from +appearing as a deletion or addition in the output. It uses the "patience +diff" algorithm internally. + --diff-algorithm={patience|minimal|histogram|myers}:: Choose a diff algorithm. The variants are as follows: + @@ -459,6 +469,12 @@ ifndef::git-format-patch[] + Also, these upper-case letters can be downcased to exclude. E.g. `--diff-filter=ad` excludes added and deleted paths. ++ +Note that not all diffs can feature all types. For instance, diffs +from the index to the working tree can never have Added entries +(because the set of paths included in the diff is limited by what is in +the index). Similarly, copied and renamed entries cannot appear if +detection for those types is disabled. -S<string>:: Look for differences that change the number of occurrences of @@ -557,6 +573,9 @@ endif::git-format-patch[] --text:: Treat all files as text. +--ignore-cr-at-eol:: + Ignore carrige-return at the end of line when doing a comparison. + --ignore-space-at-eol:: Ignore changes in whitespace at EOL. diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index b700beaff5..d50fa339dc 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git add' [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p] [--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]] - [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] + [--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing] [--renormalize] [--chmod=(+|-)x] [--] [<pathspec>...] DESCRIPTION @@ -175,6 +175,13 @@ for "git add --no-all <pathspec>...", i.e. ignored removed files. warning (e.g., if you are manually performing operations on submodules). +--renormalize:: + Apply the "clean" process freshly to all tracked files to + forcibly add them again to the index. This is useful after + changing `core.autocrlf` configuration or the `text` attribute + in order to correct files added with wrong CRLF/LF line endings. + This option implies `-u`. + --chmod=(+|-)x:: Override the executable bit of the added files. The executable bit is only changed in the index, the files on disk are left diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index d6587c5e96..b3084c99c1 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -14,7 +14,7 @@ SYNOPSIS [(--merged | --no-merged) [<commit>]] [--contains [<commit]] [--no-contains [<commit>]] [--points-at <object>] [--format=<format>] [<pattern>...] -'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>] +'git branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] 'git branch' --unset-upstream [<branchname>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> @@ -86,7 +86,7 @@ OPTIONS --delete:: Delete a branch. The branch must be fully merged in its upstream branch, or in `HEAD` if no upstream was set with - `--track` or `--set-upstream`. + `--track` or `--set-upstream-to`. -D:: Shortcut for `--delete --force`. @@ -281,6 +281,12 @@ start-point is either a local or remote-tracking branch. and the object it points at. The format is the same as that of linkgit:git-for-each-ref[1]. +CONFIGURATION +------------- +`pager.branch` is only respected when listing branches, i.e., when +`--list` is used or implied. The default is to use a pager. +See linkgit:git-config[1]. + Examples -------- diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index cf0a0b7df2..d9de992585 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -79,16 +79,21 @@ reference name expressions (see linkgit:gitrevisions[7]): With the `--branch` option, the command takes a name and checks if it can be used as a valid branch name (e.g. when creating a new -branch). The rule `git check-ref-format --branch $name` implements +branch). But be cautious when using the +previous checkout syntax that may refer to a detached HEAD state. +The rule `git check-ref-format --branch $name` implements may be stricter than what `git check-ref-format refs/heads/$name` says (e.g. a dash may appear at the beginning of a ref component, but it is explicitly forbidden at the beginning of a branch name). When run with `--branch` option in a repository, the input is first -expanded for the ``previous branch syntax'' -`@{-n}`. For example, `@{-1}` is a way to refer the last branch you -were on. This option should be used by porcelains to accept this -syntax anywhere a branch name is expected, so they can act as if you -typed the branch name. +expanded for the ``previous checkout syntax'' +`@{-n}`. For example, `@{-1}` is a way to refer the last thing that +was checked out using "git checkout" operation. This option should be +used by porcelains to accept this syntax anywhere a branch name is +expected, so they can act as if you typed the branch name. As an +exception note that, the ``previous checkout operation'' might result +in a commit object name when the N-th last thing checked out was not +a branch. OPTIONS ------- @@ -116,7 +121,7 @@ OPTIONS EXAMPLES -------- -* Print the name of the previous branch: +* Print the name of the previous thing checked out: + ------------ $ git check-ref-format --branch @{-1} diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index e108b0f74b..ca5fc9c798 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -264,6 +264,8 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. local modifications in a submodule would be overwritten the checkout will fail unless `-f` is used. If nothing (or --no-recurse-submodules) is used, the work trees of submodules will not be updated. + Just like linkgit:git-submodule[1], this will detach the + submodules HEAD. <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, @@ -272,11 +274,11 @@ section of linkgit:git-add[1] to learn how to operate the `--patch` mode. commit, your HEAD becomes "detached" and you are no longer on any branch (see below for details). + -As a special case, the `"@{-N}"` syntax for the N-th last branch/commit -checks out branches (instead of detaching). You may also specify -`-` which is synonymous with `"@{-1}"`. +You can use the `"@{-N}"` syntax to refer to the N-th last +branch/commit checked out using "git checkout" operation. You may +also specify `-` which is synonymous to `"@{-1}`. + -As a further special case, you may use `"A...B"` as a shortcut for the +As a special case, you may use `"A...B"` as a shortcut for the merge base of `A` and `B` if there is exactly one merge base. You can leave out at most one of `A` and `B`, in which case it defaults to `HEAD`. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 83c8e9b394..42ca7b5095 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -14,7 +14,7 @@ SYNOPSIS [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] [--dissociate] [--separate-git-dir <git dir>] [--depth <depth>] [--[no-]single-branch] [--no-tags] - [--recurse-submodules] [--[no-]shallow-submodules] + [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules] [--jobs <n>] [--] <repository> [<directory>] DESCRIPTION @@ -231,14 +231,17 @@ branch of some repository for search indexing. After the clone is created, initialize and clone submodules within based on the provided pathspec. If no pathspec is provided, all submodules are initialized and cloned. - Submodules are initialized and cloned using their default - settings. The resulting clone has `submodule.active` set to + This option can be given multiple times for pathspecs consisting + of multiple entries. The resulting clone has `submodule.active` set to the provided pathspec, or "." (meaning all submodules) if no - pathspec is provided. This is equivalent to running - `git submodule update --init --recursive` immediately after - the clone is finished. This option is ignored if the cloned - repository does not have a worktree/checkout (i.e. if any of - `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) + pathspec is provided. ++ +Submodules are initialized and cloned using their default settings. This is +equivalent to running +`git submodule update --init --recursive <pathspec>` immediately after +the clone is finished. This option is ignored if the cloned repository does +not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, +or `--mirror` is given) --[no-]shallow-submodules:: All submodules which are cloned will be shallow with a depth of 1. diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 4edd09fc6b..14da5fc157 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -180,6 +180,11 @@ See also <<FILES>>. value (but you can use `git config section.variable ~/` from the command line to let your shell do the expansion). +--expiry-date:: + `git config` will ensure that the output is converted from + a fixed or relative date-string to a timestamp. This option + has no effect when setting the value. + -z:: --null:: For all options that output values and/or keys, always diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index c924c945ba..e027fb8c4b 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -3,14 +3,14 @@ git-describe(1) NAME ---- -git-describe - Describe a commit using the most recent tag reachable from it - +git-describe - Give an object a human readable name based on an available ref SYNOPSIS -------- [verse] 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] [<commit-ish>...] 'git describe' [--all] [--tags] [--contains] [--abbrev=<n>] --dirty[=<mark>] +'git describe' <blob> DESCRIPTION ----------- @@ -24,6 +24,12 @@ By default (without --all or --tags) `git describe` only shows annotated tags. For more information about creating annotated tags see the -a and -s options to linkgit:git-tag[1]. +If the given object refers to a blob, it will be described +as `<commit-ish>:<path>`, such that the blob can be found +at `<path>` in the `<commit-ish>`, which itself describes the +first commit in which this blob occurs in a reverse revision walk +from HEAD. + OPTIONS ------- <commit-ish>...:: @@ -186,6 +192,14 @@ selected and output. Here fewest commits different is defined as the number of commits which would be shown by `git log tag..input` will be the smallest number of commits possible. +BUGS +---- + +Tree objects as well as tag objects not pointing at commits, cannot be described. +When describing blobs, the lightweight tags pointing at blobs are ignored, +but the blob is still described as <committ-ish>:<path> despite the lightweight +tag being favorable. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index 32246fdb00..5437f8b0f0 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -38,6 +38,13 @@ OPTIONS are shown as if 'short' were given, otherwise no ref names are shown. The default option is 'short'. +--decorate-refs=<pattern>:: +--decorate-refs-exclude=<pattern>:: + If no `--decorate-refs` is given, pretend as if all refs were + included. For each candidate, do not use it for decoration if it + matches any patterns given to `--decorate-refs-exclude` or if it + doesn't match any of the patterns given to `--decorate-refs`. + --source:: Print out the ref name given on the command line by which each commit was reached. diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index b968b64c38..502e00ec35 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -154,23 +154,71 @@ topic origin/master`, the history of remote-tracking branch `origin/master` may have been rewound and rebuilt, leading to a history of this shape: - o---B1 + o---B2 / - ---o---o---B2--o---o---o---B (origin/master) + ---o---o---B1--o---o---o---B (origin/master) \ - B3 + B0 \ - Derived (topic) + D0---D1---D (topic) -where `origin/master` used to point at commits B3, B2, B1 and now it +where `origin/master` used to point at commits B0, B1, B2 and now it points at B, and your `topic` branch was started on top of it back -when `origin/master` was at B3. This mode uses the reflog of -`origin/master` to find B3 as the fork point, so that the `topic` -can be rebased on top of the updated `origin/master` by: +when `origin/master` was at B0, and you built three commits, D0, D1, +and D, on top of it. Imagine that you now want to rebase the work +you did on the topic on top of the updated origin/master. + +In such a case, `git merge-base origin/master topic` would return the +parent of B0 in the above picture, but B0^..D is *not* the range of +commits you would want to replay on top of B (it includes B0, which +is not what you wrote; it is a commit the other side discarded when +it moved its tip from B0 to B1). + +`git merge-base --fork-point origin/master topic` is designed to +help in such a case. It takes not only B but also B0, B1, and B2 +(i.e. old tips of the remote-tracking branches your repository's +reflog knows about) into account to see on which commit your topic +branch was built and finds B0, allowing you to replay only the +commits on your topic, excluding the commits the other side later +discarded. + +Hence $ fork_point=$(git merge-base --fork-point origin/master topic) + +will find B0, and + $ git rebase --onto origin/master $fork_point topic +will replay D0, D1 and D on top of B to create a new history of this +shape: + + o---B2 + / + ---o---o---B1--o---o---o---B (origin/master) + \ \ + B0 D0'--D1'--D' (topic - updated) + \ + D0---D1---D (topic - old) + +A caveat is that older reflog entries in your repository may be +expired by `git gc`. If B0 no longer appears in the reflog of the +remote-tracking branch `origin/master`, the `--fork-point` mode +obviously cannot find it and fails, avoiding to give a random and +useless result (such as the parent of B0, like the same command +without the `--fork-point` option gives). + +Also, the remote-tracking branch you use the `--fork-point` mode +with must be the one your topic forked from its tip. If you forked +from an older commit than the tip, this mode would not find the fork +point (imagine in the above sample history B0 did not exist, +origin/master started at B1, moved to B2 and then B, and you forked +your topic at origin/master^ when origin/master was B1; the shape of +the history would be the same as above, without B0, and the parent +of B1 is what `git merge-base origin/master topic` correctly finds, +but the `--fork-point` mode will not, because it is not one of the +commits that used to be at the tip of origin/master). + See also -------- diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 43677297f3..e8dec1b3c8 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -18,7 +18,7 @@ SYNOPSIS 'git notes' merge --commit [-v | -q] 'git notes' merge --abort [-v | -q] 'git notes' remove [--ignore-missing] [--stdin] [<object>...] -'git notes' prune [-n | -v] +'git notes' prune [-n] [-v] 'git notes' get-ref diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 7436c64a95..d8c8f11c9f 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -157,6 +157,12 @@ The p4 changes will be created as the user invoking 'git p4 submit'. The according to the author of the Git commit. This option requires admin privileges in p4, which can be granted using 'p4 protect'. +To shelve changes instead of submitting, use `--shelve` and `--update-shelve`: + +---- +$ git p4 submit --shelve +$ git p4 submit --update-shelve 1234 --update-shelve 2345 +---- OPTIONS ------- @@ -310,7 +316,7 @@ These options can be used to modify 'git p4 submit' behavior. --update-shelve CHANGELIST:: Update an existing shelved changelist with this commit. Implies - --shelve. + --shelve. Repeat for multiple shelved changelists. --conflict=(ask|skip|quit):: Conflicts can occur when applying a commit to p4. When this diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 473a16135a..aa403d02f3 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -12,7 +12,8 @@ SYNOPSIS 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=<n>] [--depth=<n>] - [--revs [--unpacked | --all]] [--stdout | base-name] + [--revs [--unpacked | --all]] + [--stdout [--filter=<filter-spec>] | base-name] [--shallow] [--keep-true-parents] < object-list @@ -236,6 +237,25 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle. With this option, parents that are hidden by grafts are packed nevertheless. +--filter=<filter-spec>:: + Requires `--stdout`. Omits certain objects (usually blobs) from + the resulting packfile. See linkgit:git-rev-list[1] for valid + `<filter-spec>` forms. + +--no-filter:: + Turns off any previous `--filter=` argument. + +--missing=<missing-action>:: + A debug option to help with future "partial clone" development. + This option specifies how missing objects are handled. ++ +The form '--missing=error' requests that pack-objects stop with an error if +a missing object is encountered. This is the default action. ++ +The form '--missing=allow-any' will allow object traversal to continue +if a missing object is encountered. Missing objects will silently be +omitted from the results. + SEE ALSO -------- linkgit:git-rev-list[1] diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index 7a493c80f7..a37c0af931 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -9,7 +9,7 @@ git-prune - Prune all unreachable objects from the object database SYNOPSIS -------- [verse] -'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...] +'git prune' [-n] [-v] [--progress] [--expire <time>] [--] [<head>...] DESCRIPTION ----------- @@ -42,12 +42,15 @@ OPTIONS --verbose:: Report all removed objects. -\--:: - Do not interpret any more arguments as options. +--progress:: + Show progress. --expire <time>:: Only expire loose objects older than <time>. +\--:: + Do not interpret any more arguments as options. + <head>...:: In addition to objects reachable from any of our references, keep objects diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 3cedfb0fd2..8a861c1e0d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -203,24 +203,7 @@ Alternatively, you can undo the 'git rebase' with CONFIGURATION ------------- -rebase.stat:: - Whether to show a diffstat of what changed upstream since the last - rebase. False by default. - -rebase.autoSquash:: - If set to true enable `--autosquash` option by default. - -rebase.autoStash:: - If set to true enable `--autostash` option by default. - -rebase.missingCommitsCheck:: - If set to "warn", print warnings about removed commits in - interactive mode. If set to "error", print the warnings and - stop the rebase. If set to "ignore", no checking is - done. "ignore" by default. - -rebase.instructionFormat:: - Custom commit list format to use during an `--interactive` rebase. +include::rebase-config.txt[] OPTIONS ------- diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 44c736f1a8..472a6808cd 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -20,9 +20,9 @@ depending on the subcommand: 'git reflog' ['show'] [log-options] [<ref>] 'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] - [--dry-run] [--verbose] [--all | <refs>...] + [--dry-run | -n] [--verbose] [--all | <refs>...] 'git reflog delete' [--rewrite] [--updateref] - [--dry-run] [--verbose] ref@\{specifier\}... + [--dry-run | -n] [--verbose] ref@\{specifier\}... 'git reflog exists' <ref> Reference logs, or "reflogs", record when the tips of branches and diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index ef22f1775b..88609ff435 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -47,7 +47,9 @@ SYNOPSIS [ --fixed-strings | -F ] [ --date=<format>] [ [ --objects | --objects-edge | --objects-edge-aggressive ] - [ --unpacked ] ] + [ --unpacked ] + [ --filter=<filter-spec> [ --filter-print-omitted ] ] ] + [ --missing=<missing-action> ] [ --pretty | --header ] [ --bisect ] [ --bisect-vars ] diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index bac9014ac7..8060ea35c5 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -203,9 +203,9 @@ a password is obtained using 'git-credential'. specify a full pathname of a sendmail-like program instead; the program must support the `-i` option. Default value can be specified by the `sendemail.smtpServer` configuration - option; the built-in default is `/usr/sbin/sendmail` or - `/usr/lib/sendmail` if such program is available, or - `localhost` otherwise. + option; the built-in default is to search for `sendmail` in + `/usr/sbin`, `/usr/lib` and $PATH if such program is + available, falling back to `localhost` otherwise. --smtp-server-port=<port>:: Specifies a port different from the default port (SMTP diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 8be661007d..056dfb8661 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -175,14 +175,14 @@ create:: return its object name, without storing it anywhere in the ref namespace. This is intended to be useful for scripts. It is probably not - the command you want to use; see "save" above. + the command you want to use; see "push" above. store:: Store a given stash created via 'git stash create' (which is a dangling merge commit) in the stash ref, updating the stash reflog. This is intended to be useful for scripts. It is - probably not the command you want to use; see "save" above. + probably not the command you want to use; see "push" above. DISCUSSION ---------- diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index fc282e0a92..81cab9aefb 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -387,6 +387,19 @@ ignored submodules you can either use the --ignore-submodules=dirty command line option or the 'git submodule summary' command, which shows a similar output but does not honor these settings. +BACKGROUND REFRESH +------------------ + +By default, `git status` will automatically refresh the index, updating +the cached stat information from the working tree and writing out the +result. Writing out the updated index is an optimization that isn't +strictly necessary (`status` computes the values for itself, but writing +them out is just to save subsequent programs from repeating our +computation). When `status` is run in the background, the lock held +during the write may conflict with other simultaneous processes, causing +them to fail. Scripts running `status` in the background should consider +using `git --no-optional-locks status` (see linkgit:git[1] for details). + SEE ALSO -------- linkgit:gitignore[5] diff --git a/Documentation/git.txt b/Documentation/git.txt index 483a1f3547..3f4161a799 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -522,11 +522,10 @@ other If either of these environment variables is set then 'git fetch' and 'git push' will use the specified command instead of 'ssh' when they need to connect to a remote system. - The command will be given exactly two or four arguments: the - 'username@host' (or just 'host') from the URL and the shell - command to execute on that remote system, optionally preceded by - `-p` (literally) and the 'port' from the URL when it specifies - something other than the default SSH port. + The command-line parameters passed to the configured command are + determined by the ssh variant. See `ssh.variant` option in + linkgit:git-config[1] for details. + + `$GIT_SSH_COMMAND` takes precedence over `$GIT_SSH`, and is interpreted by the shell, which allows additional arguments to be included. @@ -705,6 +704,12 @@ of clones and fetches. which feed potentially-untrusted URLS to git commands. See linkgit:git-config[1] for more details. +`GIT_PROTOCOL`:: + For internal use only. Used in handshaking the wire protocol. + Contains a colon ':' separated list of keys with optional values + 'key[=value]'. Presence of unknown keys and values must be + ignored. + `GIT_OPTIONAL_LOCKS`:: If set to `0`, Git will complete any requested operation without performing any optional sub-operations that require taking a lock. @@ -731,6 +736,15 @@ corresponding standard handle, and if `GIT_REDIRECT_STDERR` is `2>&1`, standard error will be redirected to the same handle as standard output. +`GIT_PRINT_SHA1_ELLIPSIS` (deprecated):: + If set to `yes`, print an ellipsis following an + (abbreviated) SHA-1 value. This affects indications of + detached HEADs (linkgit:git-checkout[1]) and the raw + diff output (linkgit:git-diff[1]). Printing an + ellipsis in the cases mentioned is no longer considered + adequate and support for it is likely to be removed in the + foreseeable future (along with the variable). + Discussion[[Discussion]] ------------------------ diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 4c68bc19d5..30687de81a 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -232,8 +232,7 @@ From a clean working directory: ------------------------------------------------- $ echo "* text=auto" >.gitattributes -$ git read-tree --empty # Clean index, force re-scan of working directory -$ git add . +$ git add --renormalize . $ git status # Show files that will be normalized $ git commit -m "Introduce end-of-line normalization" ------------------------------------------------- @@ -328,6 +327,9 @@ You can declare that a filter turns a content that by itself is unusable into a usable content by setting the filter.<driver>.required configuration variable to `true`. +Note: Whenever the clean filter is changed, the repo should be renormalized: +$ git add --renormalize . + For example, in .gitattributes, you would assign the `filter` attribute for paths. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 0bb0042d8c..f877f7b7cd 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -170,7 +170,8 @@ This hook cannot affect the outcome of 'git checkout'. It is also run after 'git clone', unless the --no-checkout (-n) option is used. The first parameter given to the hook is the null-ref, the second the -ref of the new HEAD and the flag is always 1. +ref of the new HEAD and the flag is always 1. Likewise for 'git worktree add' +unless --no-checkout is used. This hook can be used to perform repository validity checks, auto-display differences from the previous HEAD if different, or set working dir metadata @@ -223,8 +224,8 @@ to the user by writing to standard error. pre-receive ~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. Just before starting to update refs on the remote repository, the pre-receive hook is invoked. Its exit status determines the success or failure of the update. @@ -264,8 +265,8 @@ linkgit:git-receive-pack[1] for some caveats. update ~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. Just before updating the ref on the remote repository, the update hook is invoked. Its exit status determines the success or failure of the ref update. @@ -309,8 +310,8 @@ unannotated tags to be pushed. post-receive ~~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated. @@ -348,8 +349,8 @@ will be set to zero, `GIT_PUSH_OPTION_COUNT=0`. post-update ~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository. +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository. It executes on the remote repository once after all the refs have been updated. @@ -379,8 +380,8 @@ for the user. push-to-checkout ~~~~~~~~~~~~~~~~ -This hook is invoked by 'git-receive-pack' on the remote repository, -which happens when a 'git push' is done on a local repository, when +This hook is invoked by 'git-receive-pack' when it reacts to +'git push' and updates reference(s) in its repository, and when the push tries to update the branch that is currently checked out and the `receive.denyCurrentBranch` configuration variable is set to `updateInstead`. Such a push by default is refused if the working diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index adf9554ad2..c60bcad44a 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -208,6 +208,10 @@ info/exclude:: 'git clean' look at it but the core Git commands do not look at it. See also: linkgit:gitignore[5]. +info/attributes:: + Defines which attributes to assign to a path, similar to per-directory + `.gitattributes` files. See also: linkgit:gitattributes[5]. + info/sparse-checkout:: This file stores sparse checkout patterns. See also: linkgit:git-read-tree[1]. diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt index 02569d0614..926e044d09 100644 --- a/Documentation/gitworkflows.txt +++ b/Documentation/gitworkflows.txt @@ -407,8 +407,8 @@ follows. `git pull <url> <branch>` ===================================== -Occasionally, the maintainer may get merge conflicts when he tries to -pull changes from downstream. In this case, he can ask downstream to +Occasionally, the maintainer may get merge conflicts when they try to +pull changes from downstream. In this case, they can ask downstream to do the merge and resolve the conflicts themselves (perhaps they will know better how to resolve them). It is one of the rare cases where downstream 'should' merge from upstream. diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh index 327f69bcf5..17231d8e59 100755 --- a/Documentation/install-doc-quick.sh +++ b/Documentation/install-doc-quick.sh @@ -3,11 +3,12 @@ repository=${1?repository} destdir=${2?destination} +GIT_MAN_REF=${3?master} -head=master GIT_DIR= +GIT_DIR= for d in "$repository/.git" "$repository" do - if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1 + if GIT_DIR="$d" git rev-parse "$GIT_MAN_REF" >/dev/null 2>&1 then GIT_DIR="$d" export GIT_DIR @@ -27,12 +28,12 @@ export GIT_INDEX_FILE GIT_WORK_TREE rm -f "$GIT_INDEX_FILE" trap 'rm -f "$GIT_INDEX_FILE"' 0 -git read-tree $head +git read-tree "$GIT_MAN_REF" git checkout-index -a -f --prefix="$destdir"/ if test -n "$GZ" then - git ls-tree -r --name-only $head | + git ls-tree -r --name-only "$GIT_MAN_REF" | xargs printf "$destdir/%s\n" | xargs gzip -f fi diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index df3ea3779b..12b6bbf591 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -26,6 +26,10 @@ merge.ff:: allowed (equivalent to giving the `--ff-only` option from the command line). +merge.verifySignatures:: + If true, this is equivalent to the --verify-signatures command + line option. See linkgit:git-merge[1] for details. + include::fmt-merge-msg-config.txt[] merge.renameLimit:: diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index a09d597463..fd5d748d1b 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -58,11 +58,12 @@ diff-algorithm=[patience|minimal|histogram|myers];; ignore-space-change;; ignore-all-space;; ignore-space-at-eol;; +ignore-cr-at-eol;; Treats lines with the indicated type of whitespace change as unchanged for the sake of a three-way merge. Whitespace changes mixed with other changes to a line are not ignored. - See also linkgit:git-diff[1] `-b`, `-w`, and - `--ignore-space-at-eol`. + See also linkgit:git-diff[1] `-b`, `-w`, + `--ignore-space-at-eol`, and `--ignore-cr-at-eol`. + * If 'their' version only introduces whitespace changes to a line, 'our' version is used; diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index d433d50f81..e664c088a5 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -204,11 +204,13 @@ endif::git-rev-list[] than given and there are spaces on its left, use those spaces - '%><(<N>)', '%><|(<N>)': similar to '% <(<N>)', '%<|(<N>)' respectively, but padding both sides (i.e. the text is centered) -- %(trailers): display the trailers of the body as interpreted by - linkgit:git-interpret-trailers[1]. If the `:only` option is given, - omit non-trailer lines from the trailer block. If the `:unfold` - option is given, behave as if interpret-trailer's `--unfold` option - was given. E.g., `%(trailers:only:unfold)` to do both. +- %(trailers[:options]): display the trailers of the body as interpreted + by linkgit:git-interpret-trailers[1]. The `trailers` string may be + followed by a colon and zero or more comma-separated options. If the + `only` option is given, omit non-trailer lines from the trailer block. + If the `unfold` option is given, behave as if interpret-trailer's + `--unfold` option was given. E.g., `%(trailers:only,unfold)` to do + both. NOTE: Some placeholders may depend on other options given to the revision traversal engine. For example, the `%g*` reflog options will diff --git a/Documentation/rebase-config.txt b/Documentation/rebase-config.txt new file mode 100644 index 0000000000..42e1ba7575 --- /dev/null +++ b/Documentation/rebase-config.txt @@ -0,0 +1,52 @@ +rebase.stat:: + Whether to show a diffstat of what changed upstream since the last + rebase. False by default. + +rebase.autoSquash:: + If set to true enable `--autosquash` option by default. + +rebase.autoStash:: + When set to true, automatically create a temporary stash entry + before the operation begins, and apply it after the operation + ends. This means that you can run rebase on a dirty worktree. + However, use with care: the final stash application after a + successful rebase might result in non-trivial conflicts. + This option can be overridden by the `--no-autostash` and + `--autostash` options of linkgit:git-rebase[1]. + Defaults to false. + +rebase.missingCommitsCheck:: + If set to "warn", git rebase -i will print a warning if some + commits are removed (e.g. a line was deleted), however the + rebase will still proceed. If set to "error", it will print + the previous warning and stop the rebase, 'git rebase + --edit-todo' can then be used to correct the error. If set to + "ignore", no checking is done. + To drop a commit without warning or error, use the `drop` + command in the todo list. + Defaults to "ignore". + +rebase.instructionFormat:: + A format string, as specified in linkgit:git-log[1], to be used for the + todo list during an interactive rebase. The format will + automatically have the long commit hash prepended to the format. + +rebase.abbreviateCommands:: + If set to true, `git rebase` will use abbreviated command names in the + todo list resulting in something like this: ++ +------------------------------------------- + p deadbee The oneline of the commit + p fa1afe1 The oneline of the next commit + ... +------------------------------------------- ++ +instead of: ++ +------------------------------------------- + pick deadbee The oneline of the commit + pick fa1afe1 The oneline of the next commit + ... +------------------------------------------- ++ +Defaults to false. diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 13501e1556..22f5c9b43d 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -686,6 +686,11 @@ ifdef::git-rev-list[] all object IDs which I need to download if I have the commit object _bar_ but not _foo_''. +--in-commit-order:: + Print tree and blob ids in order of the commits. The tree + and blob ids are printed after they are first referenced + by a commit. + --objects-edge:: Similar to `--objects`, but also print the IDs of excluded commits prefixed with a ``-'' character. This is used by @@ -706,6 +711,47 @@ ifdef::git-rev-list[] --unpacked:: Only useful with `--objects`; print the object IDs that are not in packs. + +--filter=<filter-spec>:: + Only useful with one of the `--objects*`; omits objects (usually + blobs) from the list of printed objects. The '<filter-spec>' + may be one of the following: ++ +The form '--filter=blob:none' omits all blobs. ++ +The form '--filter=blob:limit=<n>[kmg]' omits blobs larger than n bytes +or units. n may be zero. The suffixes k, m, and g can be used to name +units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same +as 'blob:limit=1024'. ++ +The form '--filter=sparse:oid=<blob-ish>' uses a sparse-checkout +specification contained in the blob (or blob-expression) '<blob-ish>' +to omit blobs that would not be not required for a sparse checkout on +the requested refs. ++ +The form '--filter=sparse:path=<path>' similarly uses a sparse-checkout +specification contained in <path>. + +--no-filter:: + Turn off any previous `--filter=` argument. + +--filter-print-omitted:: + Only useful with `--filter=`; prints a list of the objects omitted + by the filter. Object IDs are prefixed with a ``~'' character. + +--missing=<missing-action>:: + A debug option to help with future "partial clone" development. + This option specifies how missing objects are handled. ++ +The form '--missing=error' requests that rev-list stop with an error if +a missing object is encountered. This is the default action. ++ +The form '--missing=allow-any' will allow object traversal to continue +if a missing object is encountered. Missing objects will silently be +omitted from the results. ++ +The form '--missing=print' is like 'allow-any', but will also print a +list of the missing objects. Object IDs are prefixed with a ``?'' character. endif::git-rev-list[] --no-walk[=(sorted|unsorted)]:: diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 61277469c8..dfcc49c72c 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -271,7 +271,7 @@ The '..' (two-dot) Range Notation:: for commits that are reachable from r2 excluding those that are reachable from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'. -The '...' (three dot) Symmetric Difference Notation:: +The '...' (three-dot) Symmetric Difference Notation:: A similar notation 'r1\...r2' is called symmetric difference of 'r1' and 'r2' and is defined as 'r1 r2 --not $(git merge-base --all r1 r2)'. diff --git a/Documentation/technical/api-decorate.txt b/Documentation/technical/api-decorate.txt deleted file mode 100644 index 1d52a6ce14..0000000000 --- a/Documentation/technical/api-decorate.txt +++ /dev/null @@ -1,6 +0,0 @@ -decorate API -============ - -Talk about <decorate.h> - -(Linus) diff --git a/Documentation/technical/http-protocol.txt b/Documentation/technical/http-protocol.txt index 1c561bdd92..a0e45f2889 100644 --- a/Documentation/technical/http-protocol.txt +++ b/Documentation/technical/http-protocol.txt @@ -219,6 +219,10 @@ smart server reply: S: 003c2cb58b79488a98d2721cea644875a8dd0026b115 refs/tags/v1.0\n S: 003fa3c2e2402b99163d1d59756e5f207ae21cccba4c refs/tags/v1.0^{}\n +The client may send Extra Parameters (see +Documentation/technical/pack-protocol.txt) as a colon-separated string +in the Git-Protocol HTTP header. + Dumb Server Response ^^^^^^^^^^^^^^^^^^^^ Dumb servers MUST respond with the dumb server reply format. @@ -269,7 +273,11 @@ the C locale ordering. The stream SHOULD include the default ref named `HEAD` as the first ref. The stream MUST include capability declarations behind a NUL on the first ref. +The returned response contains "version 1" if "version=1" was sent as an +Extra Parameter. + smart_reply = PKT-LINE("# service=$servicename" LF) + *1("version 1") ref_list "0000" ref_list = empty_list / non_empty_list diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index ed1eae8b83..cd31edc91e 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -39,6 +39,19 @@ communicates with that invoked process over the SSH connection. The file:// transport runs the 'upload-pack' or 'receive-pack' process locally and communicates with it over a pipe. +Extra Parameters +---------------- + +The protocol provides a mechanism in which clients can send additional +information in its first message to the server. These are called "Extra +Parameters", and are supported by the Git, SSH, and HTTP protocols. + +Each Extra Parameter takes the form of `<key>=<value>` or `<key>`. + +Servers that receive any such Extra Parameters MUST ignore all +unrecognized keys. Currently, the only Extra Parameter recognized is +"version=1". + Git Transport ------------- @@ -46,18 +59,25 @@ The Git transport starts off by sending the command and repository on the wire using the pkt-line format, followed by a NUL byte and a hostname parameter, terminated by a NUL byte. - 0032git-upload-pack /project.git\0host=myserver.com\0 + 0033git-upload-pack /project.git\0host=myserver.com\0 + +The transport may send Extra Parameters by adding an additional NUL +byte, and then adding one or more NUL-terminated strings: + + 003egit-upload-pack /project.git\0host=myserver.com\0\0version=1\0 -- - git-proto-request = request-command SP pathname NUL [ host-parameter NUL ] + git-proto-request = request-command SP pathname NUL + [ host-parameter NUL ] [ NUL extra-parameters ] request-command = "git-upload-pack" / "git-receive-pack" / "git-upload-archive" ; case sensitive pathname = *( %x01-ff ) ; exclude NUL host-parameter = "host=" hostname [ ":" port ] + extra-parameters = 1*extra-parameter + extra-parameter = 1*( %x01-ff ) NUL -- -Only host-parameter is allowed in the git-proto-request. Clients -MUST NOT attempt to send additional parameters. It is used for the +host-parameter is used for the git-daemon name based virtual hosting. See --interpolated-path option to git daemon, with the %H/%CH format characters. @@ -117,6 +137,12 @@ we execute it without the leading '/'. v ssh user@example.com "git-upload-pack '~alice/project.git'" +Depending on the value of the `protocol.version` configuration variable, +Git may attempt to send Extra Parameters as a colon-separated string in +the GIT_PROTOCOL environment variable. This is done only if +the `ssh.variant` configuration variable indicates that the ssh command +supports passing environment variables as an argument. + A few things to remember here: - The "command name" is spelled with dash (e.g. git-upload-pack), but @@ -137,11 +163,13 @@ Reference Discovery ------------------- When the client initially connects the server will immediately respond -with a listing of each reference it has (all branches and tags) along +with a version number (if "version=1" is sent as an Extra Parameter), +and a listing of each reference it has (all branches and tags) along with the object name that each reference currently points to. - $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | + $ echo -e -n "0044git-upload-pack /schacon/gitbook.git\0host=example.com\0\0version=1\0" | nc -v example.com 9418 + 000aversion 1 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration @@ -165,7 +193,8 @@ immediately after the ref itself, if presented. A conforming server MUST peel the ref if it's an annotated tag. ---- - advertised-refs = (no-refs / list-of-refs) + advertised-refs = *1("version 1") + (no-refs / list-of-refs) *shallow flush-pkt diff --git a/Documentation/technical/partial-clone.txt b/Documentation/technical/partial-clone.txt new file mode 100644 index 0000000000..0bed2472c8 --- /dev/null +++ b/Documentation/technical/partial-clone.txt @@ -0,0 +1,324 @@ +Partial Clone Design Notes +========================== + +The "Partial Clone" feature is a performance optimization for Git that +allows Git to function without having a complete copy of the repository. +The goal of this work is to allow Git better handle extremely large +repositories. + +During clone and fetch operations, Git downloads the complete contents +and history of the repository. This includes all commits, trees, and +blobs for the complete life of the repository. For extremely large +repositories, clones can take hours (or days) and consume 100+GiB of disk +space. + +Often in these repositories there are many blobs and trees that the user +does not need such as: + + 1. files outside of the user's work area in the tree. For example, in + a repository with 500K directories and 3.5M files in every commit, + we can avoid downloading many objects if the user only needs a + narrow "cone" of the source tree. + + 2. large binary assets. For example, in a repository where large build + artifacts are checked into the tree, we can avoid downloading all + previous versions of these non-mergeable binary assets and only + download versions that are actually referenced. + +Partial clone allows us to avoid downloading such unneeded objects *in +advance* during clone and fetch operations and thereby reduce download +times and disk usage. Missing objects can later be "demand fetched" +if/when needed. + +Use of partial clone requires that the user be online and the origin +remote be available for on-demand fetching of missing objects. This may +or may not be problematic for the user. For example, if the user can +stay within the pre-selected subset of the source tree, they may not +encounter any missing objects. Alternatively, the user could try to +pre-fetch various objects if they know that they are going offline. + + +Non-Goals +--------- + +Partial clone is a mechanism to limit the number of blobs and trees downloaded +*within* a given range of commits -- and is therefore independent of and not +intended to conflict with existing DAG-level mechanisms to limit the set of +requested commits (i.e. shallow clone, single branch, or fetch '<refspec>'). + + +Design Overview +--------------- + +Partial clone logically consists of the following parts: + +- A mechanism for the client to describe unneeded or unwanted objects to + the server. + +- A mechanism for the server to omit such unwanted objects from packfiles + sent to the client. + +- A mechanism for the client to gracefully handle missing objects (that + were previously omitted by the server). + +- A mechanism for the client to backfill missing objects as needed. + + +Design Details +-------------- + +- A new pack-protocol capability "filter" is added to the fetch-pack and + upload-pack negotiation. + + This uses the existing capability discovery mechanism. + See "filter" in Documentation/technical/pack-protocol.txt. + +- Clients pass a "filter-spec" to clone and fetch which is passed to the + server to request filtering during packfile construction. + + There are various filters available to accommodate different situations. + See "--filter=<filter-spec>" in Documentation/rev-list-options.txt. + +- On the server pack-objects applies the requested filter-spec as it + creates "filtered" packfiles for the client. + + These filtered packfiles are *incomplete* in the traditional sense because + they may contain objects that reference objects not contained in the + packfile and that the client doesn't already have. For example, the + filtered packfile may contain trees or tags that reference missing blobs + or commits that reference missing trees. + +- On the client these incomplete packfiles are marked as "promisor packfiles" + and treated differently by various commands. + +- On the client a repository extension is added to the local config to + prevent older versions of git from failing mid-operation because of + missing objects that they cannot handle. + See "extensions.partialClone" in Documentation/technical/repository-version.txt" + + +Handling Missing Objects +------------------------ + +- An object may be missing due to a partial clone or fetch, or missing due + to repository corruption. To differentiate these cases, the local + repository specially indicates such filtered packfiles obtained from the + promisor remote as "promisor packfiles". + + These promisor packfiles consist of a "<name>.promisor" file with + arbitrary contents (like the "<name>.keep" files), in addition to + their "<name>.pack" and "<name>.idx" files. + +- The local repository considers a "promisor object" to be an object that + it knows (to the best of its ability) that the promisor remote has promised + that it has, either because the local repository has that object in one of + its promisor packfiles, or because another promisor object refers to it. + + When Git encounters a missing object, Git can see if it a promisor object + and handle it appropriately. If not, Git can report a corruption. + + This means that there is no need for the client to explicitly maintain an + expensive-to-modify list of missing objects.[a] + +- Since almost all Git code currently expects any referenced object to be + present locally and because we do not want to force every command to do + a dry-run first, a fallback mechanism is added to allow Git to attempt + to dynamically fetch missing objects from the promisor remote. + + When the normal object lookup fails to find an object, Git invokes + fetch-object to try to get the object from the server and then retry + the object lookup. This allows objects to be "faulted in" without + complicated prediction algorithms. + + For efficiency reasons, no check as to whether the missing object is + actually a promisor object is performed. + + Dynamic object fetching tends to be slow as objects are fetched one at + a time. + +- `checkout` (and any other command using `unpack-trees`) has been taught + to bulk pre-fetch all required missing blobs in a single batch. + +- `rev-list` has been taught to print missing objects. + + This can be used by other commands to bulk prefetch objects. + For example, a "git log -p A..B" may internally want to first do + something like "git rev-list --objects --quiet --missing=print A..B" + and prefetch those objects in bulk. + +- `fsck` has been updated to be fully aware of promisor objects. + +- `repack` in GC has been updated to not touch promisor packfiles at all, + and to only repack other objects. + +- The global variable "fetch_if_missing" is used to control whether an + object lookup will attempt to dynamically fetch a missing object or + report an error. + + We are not happy with this global variable and would like to remove it, + but that requires significant refactoring of the object code to pass an + additional flag. We hope that concurrent efforts to add an ODB API can + encompass this. + + +Fetching Missing Objects +------------------------ + +- Fetching of objects is done using the existing transport mechanism using + transport_fetch_refs(), setting a new transport option + TRANS_OPT_NO_DEPENDENTS to indicate that only the objects themselves are + desired, not any object that they refer to. + + Because some transports invoke fetch_pack() in the same process, fetch_pack() + has been updated to not use any object flags when the corresponding argument + (no_dependents) is set. + +- The local repository sends a request with the hashes of all requested + objects as "want" lines, and does not perform any packfile negotiation. + It then receives a packfile. + +- Because we are reusing the existing fetch-pack mechanism, fetching + currently fetches all objects referred to by the requested objects, even + though they are not necessary. + + +Current Limitations +------------------- + +- The remote used for a partial clone (or the first partial fetch + following a regular clone) is marked as the "promisor remote". + + We are currently limited to a single promisor remote and only that + remote may be used for subsequent partial fetches. + + We accept this limitation because we believe initial users of this + feature will be using it on repositories with a strong single central + server. + +- Dynamic object fetching will only ask the promisor remote for missing + objects. We assume that the promisor remote has a complete view of the + repository and can satisfy all such requests. + +- Repack essentially treats promisor and non-promisor packfiles as 2 + distinct partitions and does not mix them. Repack currently only works + on non-promisor packfiles and loose objects. + +- Dynamic object fetching invokes fetch-pack once *for each item* + because most algorithms stumble upon a missing object and need to have + it resolved before continuing their work. This may incur significant + overhead -- and multiple authentication requests -- if many objects are + needed. + +- Dynamic object fetching currently uses the existing pack protocol V0 + which means that each object is requested via fetch-pack. The server + will send a full set of info/refs when the connection is established. + If there are large number of refs, this may incur significant overhead. + + +Future Work +----------- + +- Allow more than one promisor remote and define a strategy for fetching + missing objects from specific promisor remotes or of iterating over the + set of promisor remotes until a missing object is found. + + A user might want to have multiple geographically-close cache servers + for fetching missing blobs while continuing to do filtered `git-fetch` + commands from the central server, for example. + + Or the user might want to work in a triangular work flow with multiple + promisor remotes that each have an incomplete view of the repository. + +- Allow repack to work on promisor packfiles (while keeping them distinct + from non-promisor packfiles). + +- Allow non-pathname-based filters to make use of packfile bitmaps (when + present). This was just an omission during the initial implementation. + +- Investigate use of a long-running process to dynamically fetch a series + of objects, such as proposed in [5,6] to reduce process startup and + overhead costs. + + It would be nice if pack protocol V2 could allow that long-running + process to make a series of requests over a single long-running + connection. + +- Investigate pack protocol V2 to avoid the info/refs broadcast on + each connection with the server to dynamically fetch missing objects. + +- Investigate the need to handle loose promisor objects. + + Objects in promisor packfiles are allowed to reference missing objects + that can be dynamically fetched from the server. An assumption was + made that loose objects are only created locally and therefore should + not reference a missing object. We may need to revisit that assumption + if, for example, we dynamically fetch a missing tree and store it as a + loose object rather than a single object packfile. + + This does not necessarily mean we need to mark loose objects as promisor; + it may be sufficient to relax the object lookup or is-promisor functions. + + +Non-Tasks +--------- + +- Every time the subject of "demand loading blobs" comes up it seems + that someone suggests that the server be allowed to "guess" and send + additional objects that may be related to the requested objects. + + No work has gone into actually doing that; we're just documenting that + it is a common suggestion. We're not sure how it would work and have + no plans to work on it. + + It is valid for the server to send more objects than requested (even + for a dynamic object fetch), but we are not building on that. + + +Footnotes +--------- + +[a] expensive-to-modify list of missing objects: Earlier in the design of + partial clone we discussed the need for a single list of missing objects. + This would essentially be a sorted linear list of OIDs that the were + omitted by the server during a clone or subsequent fetches. + + This file would need to be loaded into memory on every object lookup. + It would need to be read, updated, and re-written (like the .git/index) + on every explicit "git fetch" command *and* on any dynamic object fetch. + + The cost to read, update, and write this file could add significant + overhead to every command if there are many missing objects. For example, + if there are 100M missing blobs, this file would be at least 2GiB on disk. + + With the "promisor" concept, we *infer* a missing object based upon the + type of packfile that references it. + + +Related Links +------------- +[0] https://bugs.chromium.org/p/git/issues/detail?id=2 + Chromium work item for: Partial Clone + +[1] https://public-inbox.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/ + 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/ + 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/ + 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/ + 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/ + 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/user-manual.txt b/Documentation/user-manual.txt index 3a03e63eb0..eff7890274 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -319,7 +319,7 @@ do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name -HEAD is now at 427abfa... Linux v2.6.17 +HEAD is now at 427abfa Linux v2.6.17 ------------------------------------------------ The HEAD then refers to the SHA-1 of the commit instead of to a branch, @@ -508,7 +508,7 @@ Bisecting: 3537 revisions left to test after this If you run `git branch` at this point, you'll see that Git has temporarily moved you in "(no branch)". HEAD is now detached from any -branch and points directly to a commit (with commit id 65934...) that +branch and points directly to a commit (with commit id 65934) that is reachable from "master" but not from v2.6.18. Compile and test it, and see whether it crashes. Assume it does crash. Then: @@ -549,14 +549,14 @@ says "bisect". Choose a safe-looking commit nearby, note its commit id, and check it out with: ------------------------------------------------- -$ git reset --hard fb47ddb2db... +$ git reset --hard fb47ddb2db ------------------------------------------------- then test, run `bisect good` or `bisect bad` as appropriate, and continue. Instead of `git bisect visualize` and then `git reset --hard -fb47ddb2db...`, you might just want to tell Git that you want to skip +fb47ddb2db`, you might just want to tell Git that you want to skip the current commit: ------------------------------------------------- @@ -3416,7 +3416,7 @@ commit abc Author: Date: ... -:100644 100644 4b9458b... newsha... M somedirectory/myfile +:100644 100644 4b9458b newsha M somedirectory/myfile commit xyz @@ -3424,7 +3424,7 @@ Author: Date: ... -:100644 100644 oldsha... 4b9458b... M somedirectory/myfile +:100644 100644 oldsha 4b9458b M somedirectory/myfile ------------------------------------------------ This tells you that the immediately following version of the file was @@ -3449,7 +3449,7 @@ and your repository is good again! $ git log --raw --all ------------------------------------------------ -and just looked for the sha of the missing object (4b9458b..) in that +and just looked for the sha of the missing object (4b9458b) in that whole thing. It's up to you--Git does *have* a lot of information, it is just missing one particular blob version. @@ -4114,9 +4114,9 @@ program, e.g. `diff3`, `merge`, or Git's own merge-file, on the blob objects from these three stages yourself, like this: ------------------------------------------------ -$ git cat-file blob 263414f... >hello.c~1 -$ git cat-file blob 06fa6a2... >hello.c~2 -$ git cat-file blob cc44c73... >hello.c~3 +$ git cat-file blob 263414f >hello.c~1 +$ git cat-file blob 06fa6a2 >hello.c~2 +$ git cat-file blob cc44c73 >hello.c~3 $ git merge-file hello.c~2 hello.c~1 hello.c~3 ------------------------------------------------ @@ -4374,7 +4374,7 @@ $ git log --no-merges t/ ------------------------ In the pager (`less`), just search for "bundle", go a few lines back, -and see that it is in commit 18449ab0... Now just copy this object name, +and see that it is in commit 18449ab0. Now just copy this object name, and paste it into the command line ------------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index c1906f0fd1..e55ac4cb68 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.15.GIT +DEF_VER=v2.16.0-rc1 LF=' ' @@ -23,7 +23,6 @@ all:: # it at all). # # Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies BLK_SHA1. # # Define USE_LIBPCRE if you have and want to use libpcre. Various # commands such as log and grep offer runtime options to use @@ -425,6 +424,13 @@ all:: # # to say "export LESS=FRX (and LV=-c) if the environment variable # LESS (and LV) is not set, respectively". +# +# Define TEST_SHELL_PATH if you want to use a shell besides SHELL_PATH for +# running the test scripts (e.g., bash has better support for "set -x" +# tracing). +# +# When cross-compiling, define HOST_CPU as the canonical name of the CPU on +# which the built Git will run (for instance "x86_64"). GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -651,6 +657,7 @@ TEST_PROGRAMS_NEED_X += test-dump-cache-tree TEST_PROGRAMS_NEED_X += test-dump-fsmonitor TEST_PROGRAMS_NEED_X += test-dump-split-index TEST_PROGRAMS_NEED_X += test-dump-untracked-cache +TEST_PROGRAMS_NEED_X += test-example-decorate TEST_PROGRAMS_NEED_X += test-fake-ssh TEST_PROGRAMS_NEED_X += test-genrandom TEST_PROGRAMS_NEED_X += test-hashmap @@ -729,6 +736,8 @@ endif export PERL_PATH export PYTHON_PATH +TEST_SHELL_PATH = $(SHELL_PATH) + LIB_FILE = libgit.a XDIFF_LIB = xdiff/lib.a VCSSVN_LIB = vcs-svn/lib.a @@ -811,6 +820,8 @@ LIB_OBJS += levenshtein.o LIB_OBJS += line-log.o LIB_OBJS += line-range.o LIB_OBJS += list-objects.o +LIB_OBJS += list-objects-filter.o +LIB_OBJS += list-objects-filter-options.o LIB_OBJS += ll-merge.o LIB_OBJS += lockfile.o LIB_OBJS += log-tree.o @@ -850,6 +861,7 @@ LIB_OBJS += pretty.o LIB_OBJS += prio-queue.o LIB_OBJS += progress.o LIB_OBJS += prompt.o +LIB_OBJS += protocol.o LIB_OBJS += quote.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o @@ -1095,6 +1107,12 @@ else BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d' endif +ifeq (,$(HOST_CPU)) + BASIC_CFLAGS += -DGIT_HOST_CPU="\"$(firstword $(subst -, ,$(uname_M)))\"" +else + BASIC_CFLAGS += -DGIT_HOST_CPU="\"$(HOST_CPU)\"" +endif + ifneq (,$(INLINE)) BASIC_CFLAGS += -Dinline=$(INLINE) endif @@ -1256,7 +1274,6 @@ ifndef NO_OPENSSL endif else BASIC_CFLAGS += -DNO_OPENSSL - BLK_SHA1 = 1 OPENSSL_LIBSSL = endif ifdef NO_OPENSSL @@ -1725,6 +1742,7 @@ prefix_SQ = $(subst ','\'',$(prefix)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH)) TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) @@ -1893,7 +1911,9 @@ builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT version.sp version.s version.o: EXTRA_CPPFLAGS = \ '-DGIT_VERSION="$(GIT_VERSION)"' \ - '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)' + '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)' \ + '-DGIT_BUILT_FROM_COMMIT="$(shell GIT_CEILING_DIRECTORIES=\"$(CURDIR)/..\" \ + git rev-parse -q --verify HEAD || :)"' $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && \ @@ -2355,6 +2375,7 @@ GIT-LDFLAGS: FORCE # and the first level quoting from the shell that runs "echo". GIT-BUILD-OPTIONS: FORCE @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+ + @echo TEST_SHELL_PATH=\''$(subst ','\'',$(TEST_SHELL_PATH_SQ))'\' >>$@+ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+ @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+ @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+ @@ -18,6 +18,7 @@ int advice_object_name_warning = 1; int advice_rm_hints = 1; int advice_add_embedded_repo = 1; int advice_ignored_hook = 1; +int advice_waiting_for_editor = 1; static struct { const char *name; @@ -40,6 +41,7 @@ static struct { { "rmhints", &advice_rm_hints }, { "addembeddedrepo", &advice_add_embedded_repo }, { "ignoredhook", &advice_ignored_hook }, + { "waitingforeditor", &advice_waiting_for_editor }, /* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected } @@ -20,6 +20,7 @@ extern int advice_object_name_warning; extern int advice_rm_hints; extern int advice_add_embedded_repo; extern int advice_ignored_hook; +extern int advice_waiting_for_editor; int git_default_advice_config(const char *var, const char *value); __attribute__((format (printf, 1, 2))) @@ -2953,6 +2953,8 @@ static int apply_one_fragment(struct apply_state *state, newlines.len > 0 && newlines.buf[newlines.len - 1] == '\n') { old--; strbuf_setlen(&newlines, newlines.len - 1); + preimage.line_allocated[preimage.nr - 1].len--; + postimage.line_allocated[postimage.nr - 1].len--; } leading = frag->leading; @@ -229,8 +229,10 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n if (i < cnt - 1) p = p->next; } - free_commit_list(p->next); - p->next = NULL; + if (p) { + free_commit_list(p->next); + p->next = NULL; + } strbuf_release(&buf); free(array); return list; @@ -178,24 +178,40 @@ int read_branch_desc(struct strbuf *buf, const char *branch_name) return 0; } -int validate_new_branchname(const char *name, struct strbuf *ref, - int force, int attr_only) +/* + * Check if 'name' can be a valid name for a branch; die otherwise. + * Return 1 if the named branch already exists; return 0 otherwise. + * Fill ref with the full refname for the branch. + */ +int validate_branchname(const char *name, struct strbuf *ref) { if (strbuf_check_branch_ref(ref, name)) die(_("'%s' is not a valid branch name."), name); - if (!ref_exists(ref->buf)) + return ref_exists(ref->buf); +} + +/* + * Check if a branch 'name' can be created as a new branch; die otherwise. + * 'force' can be used when it is OK for the named branch already exists. + * Return 1 if the named branch already exists; return 0 otherwise. + * Fill ref with the full refname for the branch. + */ +int validate_new_branchname(const char *name, struct strbuf *ref, int force) +{ + const char *head; + + if (!validate_branchname(name, ref)) return 0; - else if (!force && !attr_only) - die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/")); - if (!attr_only) { - const char *head; + if (!force) + die(_("A branch named '%s' already exists."), + ref->buf + strlen("refs/heads/")); + + head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + if (!is_bare_repository() && head && !strcmp(head, ref->buf)) + die(_("Cannot force update the current branch.")); - head = resolve_ref_unsafe("HEAD", 0, NULL, NULL); - if (!is_bare_repository() && head && !strcmp(head, ref->buf)) - die(_("Cannot force update the current branch.")); - } return 1; } @@ -228,7 +244,7 @@ N_("\n" "\"git push -u\" to set the upstream config as you push."); void create_branch(const char *name, const char *start_name, - int force, int reflog, int clobber_head, + int force, int clobber_head_ok, int reflog, int quiet, enum branch_track track) { struct commit *commit; @@ -242,9 +258,9 @@ void create_branch(const char *name, const char *start_name, if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE) explicit_tracking = 1; - if (validate_new_branchname(name, &ref, force, - track == BRANCH_TRACK_OVERRIDE || - clobber_head)) { + if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok) + ? validate_branchname(name, &ref) + : validate_new_branchname(name, &ref, force)) { if (!force) dont_change_ref = 1; else @@ -13,32 +13,35 @@ * * - force enables overwriting an existing (non-head) branch * + * - clobber_head_ok allows the currently checked out (hence existing) + * branch to be overwritten; without 'force', it has no effect. + * * - reflog creates a reflog for the branch * + * - quiet suppresses tracking information + * * - track causes the new branch to be configured to merge the remote branch * that start_name is a tracking branch for (if any). + * */ void create_branch(const char *name, const char *start_name, - int force, int reflog, - int clobber_head, int quiet, enum branch_track track); + int force, int clobber_head_ok, + int reflog, int quiet, enum branch_track track); /* - * Validates that the requested branch may be created, returning the - * interpreted ref in ref, force indicates whether (non-head) branches - * may be overwritten. A non-zero return value indicates that the force - * parameter was non-zero and the branch already exists. - * - * Contrary to all of the above, when attr_only is 1, the caller is - * not interested in verifying if it is Ok to update the named - * branch to point at a potentially different commit. It is merely - * asking if it is OK to change some attribute for the named branch - * (e.g. tracking upstream). - * - * NEEDSWORK: This needs to be split into two separate functions in the - * longer run for sanity. - * + * Check if 'name' can be a valid name for a branch; die otherwise. + * Return 1 if the named branch already exists; return 0 otherwise. + * Fill ref with the full refname for the branch. + */ +extern int validate_branchname(const char *name, struct strbuf *ref); + +/* + * Check if a branch 'name' can be created as a new branch; die otherwise. + * 'force' can be used when it is OK for the named branch already exists. + * Return 1 if the named branch already exists; return 0 otherwise. + * Fill ref with the full refname for the branch. */ -int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only); +extern int validate_new_branchname(const char *name, struct strbuf *ref, int force); /* * Remove information about the state of working on the current diff --git a/builtin/add.c b/builtin/add.c index 8d08e99e92..bf01d89e28 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -26,6 +26,7 @@ static const char * const builtin_add_usage[] = { }; static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; +static int add_renormalize; struct update_callback_data { int flags; @@ -123,6 +124,25 @@ int add_files_to_cache(const char *prefix, return !!data.add_errors; } +static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) +{ + int i, retval = 0; + + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + + if (ce_stage(ce)) + continue; /* do not touch unmerged paths */ + if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode)) + continue; /* do not touch non blobs */ + if (pathspec && !ce_path_match(ce, pathspec, NULL)) + continue; + retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE); + } + + return retval; +} + static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, int prefix) { char *seen; @@ -276,6 +296,7 @@ static struct option builtin_add_options[] = { OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files")), OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), + OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")), { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit, @@ -406,7 +427,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) chmod_arg[1] != 'x' || chmod_arg[2])) die(_("--chmod param '%s' must be either -x or +x"), chmod_arg); - add_new_files = !take_worktree_changes && !refresh_only; + add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); @@ -500,7 +521,10 @@ int cmd_add(int argc, const char **argv, const char *prefix) plug_bulk_checkin(); - exit_status |= add_files_to_cache(prefix, &pathspec, flags); + if (add_renormalize) + exit_status |= renormalize_tracked_files(&pathspec, flags); + else + exit_status |= add_files_to_cache(prefix, &pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); diff --git a/builtin/am.c b/builtin/am.c index 02853b3e05..acfe9d3c8c 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -708,6 +708,7 @@ static int split_mail_mbox(struct am_state *state, const char **paths, { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf last = STRBUF_INIT; + int ret; cp.git_cmd = 1; argv_array_push(&cp.args, "mailsplit"); @@ -721,13 +722,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths, argv_array_push(&cp.args, "--"); argv_array_pushv(&cp.args, paths); - if (capture_command(&cp, &last, 8)) - return -1; + ret = capture_command(&cp, &last, 8); + if (ret) + goto exit; state->cur = 1; state->last = strtol(last.buf, NULL, 10); - return 0; +exit: + strbuf_release(&last); + return ret ? -1 : 0; } /** @@ -1143,43 +1147,6 @@ static void refresh_and_write_cache(void) } /** - * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn - * branch, returns 1 if there are entries in the index, 0 otherwise. If an - * strbuf is provided, the space-separated list of files that differ will be - * appended to it. - */ -static int index_has_changes(struct strbuf *sb) -{ - struct object_id head; - int i; - - if (!get_oid_tree("HEAD", &head)) { - struct diff_options opt; - - diff_setup(&opt); - opt.flags.exit_with_status = 1; - if (!sb) - opt.flags.quick = 1; - do_diff_cache(&head, &opt); - diffcore_std(&opt); - for (i = 0; sb && i < diff_queued_diff.nr; i++) { - if (i) - strbuf_addch(sb, ' '); - strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); - } - diff_flush(&opt); - return opt.flags.has_changes != 0; - } else { - for (i = 0; sb && i < active_nr; i++) { - if (i) - strbuf_addch(sb, ' '); - strbuf_addstr(sb, active_cache[i]->name); - } - return !!active_nr; - } -} - -/** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ @@ -1433,7 +1400,7 @@ static void write_index_patch(const struct am_state *state) if (!get_oid_tree("HEAD", &head)) tree = lookup_tree(&head); else - tree = lookup_tree(&empty_tree_oid); + tree = lookup_tree(the_hash_algo->empty_tree); fp = xfopen(am_path(state, "patch"), "w"); init_revisions(&rev_info, NULL); diff --git a/builtin/branch.c b/builtin/branch.c index 33fd5fcfd1..8dcc2ed058 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -462,8 +462,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; + const char *interpreted_oldname = NULL; + const char *interpreted_newname = NULL; int recovery = 0; - int clobber_head_ok; if (!oldname) { if (copy) @@ -487,12 +488,18 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int * A command like "git branch -M currentbranch currentbranch" cannot * cause the worktree to become inconsistent with HEAD, so allow it. */ - clobber_head_ok = !strcmp(oldname, newname); - - validate_new_branchname(newname, &newref, force, clobber_head_ok); + if (!strcmp(oldname, newname)) + validate_branchname(newname, &newref); + else + validate_new_branchname(newname, &newref, force); reject_rebase_or_bisect_branch(oldref.buf); + if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) || + !skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) { + die("BUG: expected prefix missing for refs"); + } + if (copy) strbuf_addf(&logmsg, "Branch: copied %s to %s", oldref.buf, newref.buf); @@ -507,11 +514,11 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int if (recovery) { if (copy) - warning(_("Copied a misnamed branch '%s' away"), - oldref.buf + 11); + warning(_("Created a copy of a misnamed branch '%s'"), + interpreted_oldname); else warning(_("Renamed a misnamed branch '%s' away"), - oldref.buf + 11); + interpreted_oldname); } if (!copy && @@ -520,9 +527,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int strbuf_release(&logmsg); - strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); + strbuf_addf(&oldsection, "branch.%s", interpreted_oldname); strbuf_release(&oldref); - strbuf_addf(&newsection, "branch.%s", newref.buf + 11); + strbuf_addf(&newsection, "branch.%s", interpreted_newname); strbuf_release(&newref); if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0) die(_("Branch is renamed, but update of config-file failed")); @@ -675,6 +682,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) copy *= 2; } + if (list) + setup_auto_pager("branch", 1); + if (delete) { if (!argc) die(_("branch name required")); @@ -793,9 +803,6 @@ int cmd_branch(int argc, const char **argv, const char *prefix) } else if (argc > 0 && argc <= 2) { struct branch *branch = branch_get(argv[0]); - if (!strcmp(argv[0], "HEAD")) - die(_("it does not make sense to create 'HEAD' manually")); - if (!branch) die(_("no such branch '%s'"), argv[0]); @@ -806,7 +813,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); create_branch(argv[0], (argc == 2) ? argv[1] : head, - force, reflog, 0, quiet, track); + force, 0, reflog, quiet, track); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/checkout.c b/builtin/checkout.c index ad8f94044c..8bdc927d3f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -401,10 +401,16 @@ static void show_local_changes(struct object *head, static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; + if (!parse_commit(commit)) pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb); - fprintf(stderr, "%s %s... %s\n", msg, - find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + if (print_sha1_ellipsis()) { + fprintf(stderr, "%s %s... %s\n", msg, + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + } else { + fprintf(stderr, "%s %s %s\n", msg, + find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf); + } strbuf_release(&sb); } @@ -515,7 +521,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } tree = parse_tree_indirect(old->commit ? &old->commit->object.oid : - &empty_tree_oid); + the_hash_algo->empty_tree); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(&new->commit->object.oid); init_tree_desc(&trees[1], tree->buffer, tree->size); @@ -641,8 +647,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, else create_branch(opts->new_branch, new->name, opts->new_branch_force ? 1 : 0, - opts->new_branch_log, opts->new_branch_force ? 1 : 0, + opts->new_branch_log, opts->quiet, opts->track); new->name = opts->new_branch; @@ -1248,11 +1254,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - opts.branch_exists = - validate_new_branchname(opts.new_branch, &buf, - !!opts.new_branch_force, - !!opts.new_branch_force); - + if (opts.new_branch_force) + opts.branch_exists = validate_branchname(opts.new_branch, &buf); + else + opts.branch_exists = + validate_new_branchname(opts.new_branch, &buf, 0); strbuf_release(&buf); } diff --git a/builtin/clone.c b/builtin/clone.c index b22845738a..2da71db107 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -452,7 +452,8 @@ static void clone_local(const char *src_repo, const char *dest_repo) { if (option_shared) { struct strbuf alt = STRBUF_INIT; - strbuf_addf(&alt, "%s/objects", src_repo); + get_common_dir(&alt, src_repo); + strbuf_addstr(&alt, "/objects"); add_to_alternates_file(alt.buf); strbuf_release(&alt); } else { @@ -1082,9 +1083,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) warning(_("--local is ignored")); transport->cloning = 1; - if (!transport->get_refs_list || (!is_local && !transport->fetch)) - die(_("Don't know how to clone %s"), transport->url); - transport_set_option(transport, TRANS_OPT_KEEP, "yes"); if (option_depth) diff --git a/builtin/config.c b/builtin/config.c index d13daeeb55..ab5f95476e 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -52,6 +52,7 @@ static int show_origin; #define TYPE_INT (1<<1) #define TYPE_BOOL_OR_INT (1<<2) #define TYPE_PATH (1<<3) +#define TYPE_EXPIRY_DATE (1<<4) static struct option builtin_config_options[] = { OPT_GROUP(N_("Config file location")), @@ -80,6 +81,7 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "int", &types, N_("value is decimal number"), TYPE_INT), OPT_BIT(0, "bool-or-int", &types, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), OPT_BIT(0, "path", &types, N_("value is a path (file or directory name)"), TYPE_PATH), + OPT_BIT(0, "expiry-date", &types, N_("value is an expiry date"), TYPE_EXPIRY_DATE), OPT_GROUP(N_("Other")), OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), @@ -159,6 +161,11 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value return -1; strbuf_addstr(buf, v); free((char *)v); + } else if (types == TYPE_EXPIRY_DATE) { + timestamp_t t; + if (git_config_expiry_date(&t, key_, value_) < 0) + return -1; + strbuf_addf(buf, "%"PRItime, t); } else if (value_) { strbuf_addstr(buf, value_); } else { @@ -273,12 +280,13 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (types == 0 || types == TYPE_PATH) + if (types == 0 || types == TYPE_PATH || types == TYPE_EXPIRY_DATE) /* * We don't do normalization for TYPE_PATH here: If * the path is like ~/foobar/, we prefer to store * "~/foobar/" in the config file, and to expand the ~ * when retrieving the value. + * Also don't do normalization for expiry dates. */ return xstrdup(value); if (types == TYPE_INT) diff --git a/builtin/describe.c b/builtin/describe.c index e14e162ef6..3b0b204b1e 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -3,6 +3,7 @@ #include "lockfile.h" #include "commit.h" #include "tag.h" +#include "blob.h" #include "refs.h" #include "builtin.h" #include "exec_cmd.h" @@ -12,6 +13,8 @@ #include "hashmap.h" #include "argv-array.h" #include "run-command.h" +#include "revision.h" +#include "list-objects.h" #define MAX_TAGS (FLAG_BITS - 1) @@ -256,7 +259,7 @@ static unsigned long finish_depth_computation( return seen_commits; } -static void display_name(struct commit_name *n) +static void append_name(struct commit_name *n, struct strbuf *dst) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(&n->oid); @@ -272,19 +275,18 @@ static void display_name(struct commit_name *n) } if (n->tag) - printf("%s", n->tag->tag); + strbuf_addstr(dst, n->tag->tag); else - printf("%s", n->path); + strbuf_addstr(dst, n->path); } -static void show_suffix(int depth, const struct object_id *oid) +static void append_suffix(int depth, const struct object_id *oid, struct strbuf *dst) { - printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); + strbuf_addf(dst, "-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev)); } -static void describe(const char *arg, int last_one) +static void describe_commit(struct object_id *oid, struct strbuf *dst) { - struct object_id oid; struct commit *cmit, *gave_up_on = NULL; struct commit_list *list; struct commit_name *n; @@ -293,30 +295,25 @@ static void describe(const char *arg, int last_one) unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - if (get_oid(arg, &oid)) - die(_("Not a valid object name %s"), arg); - cmit = lookup_commit_reference(&oid); - if (!cmit) - die(_("%s is not a valid '%s' object"), arg, commit_type); + cmit = lookup_commit_reference(oid); n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* * Exact match to an existing ref. */ - display_name(n); + append_name(n, dst); if (longformat) - show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid); + append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); return; } if (!max_candidates) die(_("no tag exactly matches '%s'"), oid_to_hex(&cmit->object.oid)); if (debug) - fprintf(stderr, _("searching to describe %s\n"), arg); + fprintf(stderr, _("No exact match on refs or tags, searching to describe\n")); if (!have_util) { struct hashmap_iter iter; @@ -381,22 +378,21 @@ static void describe(const char *arg, int last_one) } if (!match_cnt) { - struct object_id *oid = &cmit->object.oid; + struct object_id *cmit_oid = &cmit->object.oid; if (always) { - printf("%s", find_unique_abbrev(oid->hash, abbrev)); + strbuf_addstr(dst, find_unique_abbrev(cmit_oid->hash, abbrev)); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); return; } if (unannotated_cnt) die(_("No annotated tags can describe '%s'.\n" "However, there were unannotated tags: try --tags."), - oid_to_hex(oid)); + oid_to_hex(cmit_oid)); else die(_("No tags can describe '%s'.\n" "Try --always, or create some tags."), - oid_to_hex(oid)); + oid_to_hex(cmit_oid)); } QSORT(all_matches, match_cnt, compare_pt); @@ -434,15 +430,86 @@ static void describe(const char *arg, int last_one) } } - display_name(all_matches[0].name); + append_name(all_matches[0].name, dst); if (abbrev) - show_suffix(all_matches[0].depth, &cmit->object.oid); + append_suffix(all_matches[0].depth, &cmit->object.oid, dst); if (suffix) - printf("%s", suffix); - printf("\n"); + strbuf_addstr(dst, suffix); +} + +struct process_commit_data { + struct object_id current_commit; + struct object_id looking_for; + struct strbuf *dst; + struct rev_info *revs; +}; + +static void process_commit(struct commit *commit, void *data) +{ + struct process_commit_data *pcd = data; + pcd->current_commit = commit->object.oid; +} + +static void process_object(struct object *obj, const char *path, void *data) +{ + struct process_commit_data *pcd = data; + + if (!oidcmp(&pcd->looking_for, &obj->oid) && !pcd->dst->len) { + reset_revision_walk(); + describe_commit(&pcd->current_commit, pcd->dst); + strbuf_addf(pcd->dst, ":%s", path); + free_commit_list(pcd->revs->commits); + pcd->revs->commits = NULL; + } +} + +static void describe_blob(struct object_id oid, struct strbuf *dst) +{ + struct rev_info revs; + struct argv_array args = ARGV_ARRAY_INIT; + struct process_commit_data pcd = { null_oid, oid, dst, &revs}; + + argv_array_pushl(&args, "internal: The first arg is not parsed", + "--objects", "--in-commit-order", "--reverse", "HEAD", + NULL); + + init_revisions(&revs, NULL); + if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1) + BUG("setup_revisions could not handle all args?"); + + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + + traverse_commit_list(&revs, process_commit, process_object, &pcd); + reset_revision_walk(); +} + +static void describe(const char *arg, int last_one) +{ + struct object_id oid; + struct commit *cmit; + struct strbuf sb = STRBUF_INIT; + + if (debug) + fprintf(stderr, _("describe %s\n"), arg); + + if (get_oid(arg, &oid)) + die(_("Not a valid object name %s"), arg); + cmit = lookup_commit_reference_gently(&oid, 1); + + if (cmit) + describe_commit(&oid, &sb); + else if (lookup_blob(&oid)) + describe_blob(oid, &sb); + else + die(_("%s is neither a commit nor blob"), arg); + + puts(sb.buf); if (!last_one) clear_commit_marks(cmit, -1); + + strbuf_release(&sb); } int cmd_describe(int argc, const char **argv, const char *prefix) diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index d66499909e..b775a75647 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -110,6 +110,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ init_revisions(opt, prefix); + if (read_cache() < 0) + die(_("index file corrupt")); opt->abbrev = 0; opt->diff = 1; opt->disable_stdin = 1; diff --git a/builtin/diff.c b/builtin/diff.c index 9808d062a8..16bfb22f73 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -379,7 +379,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) add_head_to_pending(&rev); if (!rev.pending.nr) { struct tree *tree; - tree = lookup_tree(&empty_tree_oid); + tree = lookup_tree(the_hash_algo->empty_tree); add_pending_object(&rev, &tree->object, "HEAD"); } break; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index f8fe04ca53..796d0cd66c 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -895,7 +895,7 @@ static void export_marks(char *file) { unsigned int i; uint32_t mark; - struct object_decoration *deco = idnums.hash; + struct decoration_entry *deco = idnums.entries; FILE *f; int e = 0; diff --git a/builtin/fetch.c b/builtin/fetch.c index e705237fa9..7bbcd26faf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -3,6 +3,7 @@ */ #include "cache.h" #include "config.h" +#include "repository.h" #include "refs.h" #include "commit.h" #include "builtin.h" @@ -1094,9 +1095,6 @@ static int do_fetch(struct transport *transport, tags = TAGS_UNSET; } - if (!transport->get_refs_list || !transport->fetch) - die(_("Don't know how to fetch from %s"), transport->url); - /* if not appending, truncate FETCH_HEAD */ if (!append && !dry_run) { retcode = truncate_fetch_head(); @@ -1397,7 +1395,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) struct argv_array options = ARGV_ARRAY_INIT; add_options_to_argv(&options); - result = fetch_populated_submodules(&options, + result = fetch_populated_submodules(the_repository, + &options, submodule_prefix, recurse_submodules, recurse_submodules_default, diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 22034f87e7..8e8a15ea4a 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -377,7 +377,8 @@ static void shortlog(const char *name, string_list_append(&subjects, oid_to_hex(&commit->object.oid)); else - string_list_append(&subjects, strbuf_detach(&sb, NULL)); + string_list_append_nodup(&subjects, + strbuf_detach(&sb, NULL)); } if (opts->credit_people) diff --git a/builtin/grep.c b/builtin/grep.c index 5a6cfe6b45..3ca4ac80d8 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -1015,6 +1015,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) prefix, argv + i); pathspec.max_depth = opt.max_depth; pathspec.recursive = 1; + pathspec.recurse_submodules = !!recurse_submodules; #ifndef NO_PTHREADS if (list.nr || cached || show_in_pager) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 8ec459f522..4c51aec81f 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1660,10 +1660,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) from_stdin = 1; } else if (!strcmp(arg, "--fix-thin")) { fix_thin_pack = 1; - } else if (!strcmp(arg, "--strict")) { - strict = 1; - do_fsck_object = 1; - } else if (skip_prefix(arg, "--strict=", &arg)) { + } else if (skip_to_optional_arg(arg, "--strict", &arg)) { strict = 1; do_fsck_object = 1; fsck_set_msg_types(&fsck_options, arg); @@ -1679,10 +1676,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) verify = 1; show_stat = 1; stat_only = 1; - } else if (!strcmp(arg, "--keep")) { - keep_msg = ""; - } else if (starts_with(arg, "--keep=")) { - keep_msg = arg + 7; + } else if (skip_to_optional_arg(arg, "--keep", &keep_msg)) { + ; /* nothing to do */ } else if (starts_with(arg, "--threads=")) { char *end; nr_threads = strtoul(arg+10, &end, 0); diff --git a/builtin/log.c b/builtin/log.c index 6c1fa896ad..14fdf39165 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -142,11 +142,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct userformat_want w; int quiet = 0, source = 0, mailmap = 0; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; + static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; + static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; + struct decoration_filter decoration_filter = {&decorate_refs_include, + &decorate_refs_exclude}; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, + N_("pattern"), N_("only decorate refs that match <pattern>")), + OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, + N_("pattern"), N_("do not decorate refs that match <pattern>")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", @@ -205,7 +213,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (decoration_style) { rev->show_decorations = 1; - load_ref_decorations(decoration_style); + load_ref_decorations(&decoration_filter, decoration_style); } if (rev->line_level_traverse) diff --git a/builtin/merge.c b/builtin/merge.c index 612dd7bfb6..30264cfd7c 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -567,6 +567,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat")) show_diffstat = git_config_bool(k, v); + else if (!strcmp(k, "merge.verifysignatures")) + verify_signatures = git_config_bool(k, v); else if (!strcmp(k, "pull.twohead")) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) diff --git a/builtin/mv.c b/builtin/mv.c index ffdd5f01a1..cf3684d907 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -291,7 +291,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } if (gitmodules_modified) - stage_updated_gitmodules(); + stage_updated_gitmodules(&the_index); if (active_cache_changed && write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) diff --git a/builtin/notes.c b/builtin/notes.c index d7754db143..7c81761645 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -12,7 +12,7 @@ #include "builtin.h" #include "notes.h" #include "blob.h" -#include "commit.h" +#include "pretty.h" #include "refs.h" #include "exec_cmd.h" #include "run-command.h" @@ -33,7 +33,7 @@ static const char * const git_notes_usage[] = { N_("git notes merge --commit [-v | -q]"), N_("git notes merge --abort [-v | -q]"), N_("git notes [--ref <notes-ref>] remove [<object>...]"), - N_("git notes [--ref <notes-ref>] prune [-n | -v]"), + N_("git notes [--ref <notes-ref>] prune [-n] [-v]"), N_("git notes [--ref <notes-ref>] get-ref"), NULL }; @@ -865,10 +865,10 @@ static int merge(int argc, const char **argv, const char *prefix) if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die(_("failed to store link to current notes ref (%s)"), default_notes_ref()); - printf(_("Automatic notes merge failed. Fix conflicts in %s and " - "commit the result with 'git notes merge --commit', or " - "abort the merge with 'git notes merge --abort'.\n"), - git_path(NOTES_MERGE_WORKTREE)); + fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s " + "and commit the result with 'git notes merge --commit', " + "or abort the merge with 'git notes merge --abort'.\n"), + git_path(NOTES_MERGE_WORKTREE)); } free_notes(t); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 631de28761..6b9cfc289d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -15,6 +15,8 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" #include "pack-objects.h" #include "progress.h" #include "refs.h" @@ -79,6 +81,15 @@ static unsigned long cache_max_small_delta_size = 1000; static unsigned long window_memory_limit = 0; +static struct list_objects_filter_options filter_options; + +enum missing_action { + MA_ERROR = 0, /* fail if any missing objects are encountered */ + MA_ALLOW_ANY, /* silently allow ALL missing objects */ +}; +static enum missing_action arg_missing_action; +static show_object_fn fn_show_object; + /* * stats */ @@ -2553,6 +2564,42 @@ static void show_object(struct object *obj, const char *name, void *data) obj->flags |= OBJECT_ADDED; } +static void show_object__ma_allow_any(struct object *obj, const char *name, void *data) +{ + assert(arg_missing_action == MA_ALLOW_ANY); + + /* + * Quietly ignore ALL missing objects. This avoids problems with + * staging them now and getting an odd error later. + */ + if (!has_object_file(&obj->oid)) + return; + + show_object(obj, name, data); +} + +static int option_parse_missing_action(const struct option *opt, + const char *arg, int unset) +{ + assert(arg); + assert(!unset); + + if (!strcmp(arg, "error")) { + arg_missing_action = MA_ERROR; + fn_show_object = show_object; + return 0; + } + + if (!strcmp(arg, "allow-any")) { + arg_missing_action = MA_ALLOW_ANY; + fn_show_object = show_object__ma_allow_any; + return 0; + } + + die(_("invalid value for --missing")); + return 0; +} + static void show_edge(struct commit *commit) { add_preferred_base(&commit->object.oid); @@ -2817,7 +2864,12 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(&revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object, NULL); + + if (!fn_show_object) + fn_show_object = show_object; + traverse_commit_list_filtered(&filter_options, &revs, + show_commit, fn_show_object, NULL, + NULL); if (unpack_unreachable_expiration) { revs.ignore_missing_links = 1; @@ -2953,6 +3005,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("use a bitmap index if available to speed up counting objects")), OPT_BOOL(0, "write-bitmap-index", &write_bitmap_index, N_("write a bitmap index together with the pack index")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + { OPTION_CALLBACK, 0, "missing", NULL, N_("action"), + N_("handling for missing objects"), PARSE_OPT_NONEG, + option_parse_missing_action }, OPT_END(), }; @@ -3029,6 +3085,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; + if (filter_options.choice) { + if (!pack_to_stdout) + die("cannot use --filter without --stdout."); + use_bitmap_index = 0; + } + /* * "soft" reasons not to use bitmaps - for on-disk repack by default we want * diff --git a/builtin/prune.c b/builtin/prune.c index cddabf26a9..d2fdae680a 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -8,7 +8,7 @@ #include "progress.h" static const char * const prune_usage[] = { - N_("git prune [-n] [-v] [--expire <time>] [--] [<head>...]"), + N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), NULL }; static int show_only; diff --git a/builtin/pull.c b/builtin/pull.c index f7e2c4f2ec..511dbbe0f6 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -113,6 +113,8 @@ static char *opt_depth; static char *opt_unshallow; static char *opt_update_shallow; static char *opt_refmap; +static char *opt_ipv4; +static char *opt_ipv6; static struct option pull_options[] = { /* Shared options */ @@ -218,6 +220,12 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG), + OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, + N_("use IPv4 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, + N_("use IPv6 addresses only"), + PARSE_OPT_NOARG), OPT_END() }; @@ -522,6 +530,10 @@ static int run_fetch(const char *repo, const char **refspecs) argv_array_push(&args, opt_update_shallow); if (opt_refmap) argv_array_push(&args, opt_refmap); + if (opt_ipv4) + argv_array_push(&args, opt_ipv4); + if (opt_ipv6) + argv_array_push(&args, opt_ipv6); if (repo) { argv_array_push(&args, repo); @@ -545,7 +557,7 @@ static int pull_into_void(const struct object_id *merge_head, * index/worktree changes that the user already made on the unborn * branch. */ - if (checkout_fast_forward(&empty_tree_oid, merge_head, 0)) + if (checkout_fast_forward(the_hash_algo->empty_tree, merge_head, 0)) return 1; if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR)) diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index f8519363a3..7daee544b7 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -12,10 +12,12 @@ static const char * const builtin_rebase_helper_usage[] = { int cmd_rebase__helper(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; - int keep_empty = 0; + unsigned flags = 0, keep_empty = 0; + int abbreviate_commands = 0; enum { - CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_SHA1S, EXPAND_SHA1S, - CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH + CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS, + CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH, + ADD_EXEC } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -27,19 +29,22 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "make-script", &command, N_("make rebase script"), MAKE_SCRIPT), OPT_CMDMODE(0, "shorten-ids", &command, - N_("shorten SHA-1s in the todo list"), SHORTEN_SHA1S), + N_("shorten commit ids in the todo list"), SHORTEN_OIDS), OPT_CMDMODE(0, "expand-ids", &command, - N_("expand SHA-1s in the todo list"), EXPAND_SHA1S), + N_("expand commit ids in the todo list"), EXPAND_OIDS), OPT_CMDMODE(0, "check-todo-list", &command, N_("check the todo list"), CHECK_TODO_LIST), OPT_CMDMODE(0, "skip-unnecessary-picks", &command, N_("skip unnecessary picks"), SKIP_UNNECESSARY_PICKS), OPT_CMDMODE(0, "rearrange-squash", &command, N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), + OPT_CMDMODE(0, "add-exec-commands", &command, + N_("insert exec commands in todo list"), ADD_EXEC), OPT_END() }; git_config(git_default_config, NULL); + git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); opts.action = REPLAY_INTERACTIVE_REBASE; opts.allow_ff = 1; @@ -48,21 +53,25 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, NULL, options, builtin_rebase_helper_usage, PARSE_OPT_KEEP_ARGV0); + flags |= keep_empty ? TODO_LIST_KEEP_EMPTY : 0; + flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; + flags |= command == SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + if (command == CONTINUE && argc == 1) return !!sequencer_continue(&opts); if (command == ABORT && argc == 1) return !!sequencer_remove_state(&opts); if (command == MAKE_SCRIPT && argc > 1) - return !!sequencer_make_script(keep_empty, stdout, argc, argv); - if (command == SHORTEN_SHA1S && argc == 1) - return !!transform_todo_ids(1); - if (command == EXPAND_SHA1S && argc == 1) - return !!transform_todo_ids(0); + return !!sequencer_make_script(stdout, argc, argv, flags); + if ((command == SHORTEN_OIDS || command == EXPAND_OIDS) && argc == 1) + return !!transform_todos(flags); if (command == CHECK_TODO_LIST && argc == 1) return !!check_todo_list(); if (command == SKIP_UNNECESSARY_PICKS && argc == 1) return !!skip_unnecessary_picks(); if (command == REARRANGE_SQUASH && argc == 1) return !!rearrange_squash(); + if (command == ADD_EXEC && argc == 2) + return !!sequencer_add_exec_commands(argv[1]); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 4d37a160d7..b7ce7c7f52 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -24,6 +24,7 @@ #include "tmp-objdir.h" #include "oidset.h" #include "packfile.h" +#include "protocol.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -1961,6 +1962,22 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; + switch (determine_protocol_version_server()) { + case protocol_v1: + /* + * v1 is just the original protocol with a version string, + * so just fall through after writing the version string. + */ + if (advertise_refs || !stateless_rpc) + packet_write_fmt(1, "version 1\n"); + + /* fallthrough */ + case protocol_v0: + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + if (advertise_refs || !stateless_rpc) { write_head_info(); } diff --git a/builtin/reflog.c b/builtin/reflog.c index ab31a3b6aa..2233725315 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -416,16 +416,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) return ent; } -static int parse_expire_cfg_value(const char *var, const char *value, timestamp_t *expire) -{ - if (!value) - return config_error_nonbool(var); - if (parse_expiry_date(value, expire)) - return error(_("'%s' for '%s' is not a valid timestamp"), - value, var); - return 0; -} - /* expiry timer slot */ #define EXPIRE_TOTAL 01 #define EXPIRE_UNREACH 02 @@ -443,11 +433,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) if (!strcmp(key, "reflogexpire")) { slot = EXPIRE_TOTAL; - if (parse_expire_cfg_value(var, value, &expire)) + if (git_config_expiry_date(&expire, var, value)) return -1; } else if (!strcmp(key, "reflogexpireunreachable")) { slot = EXPIRE_UNREACH; - if (parse_expire_cfg_value(var, value, &expire)) + if (git_config_expiry_date(&expire, var, value)) return -1; } else return git_default_config(var, value, cb); diff --git a/builtin/reset.c b/builtin/reset.c index 906e541658..e15f595799 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -12,7 +12,7 @@ #include "lockfile.h" #include "tag.h" #include "object.h" -#include "commit.h" +#include "pretty.h" #include "run-command.h" #include "refs.h" #include "diff.h" diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 4032eb3811..d5345b6a2e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -4,6 +4,8 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" #include "pack.h" #include "pack-bitmap.h" #include "builtin.h" @@ -12,6 +14,7 @@ #include "bisect.h" #include "progress.h" #include "reflog-walk.h" +#include "oidset.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -55,6 +58,20 @@ static const char rev_list_usage[] = static struct progress *progress; static unsigned progress_counter; +static struct list_objects_filter_options filter_options; +static struct oidset omitted_objects; +static int arg_print_omitted; /* print objects omitted by filter */ + +static struct oidset missing_objects; +enum missing_action { + MA_ERROR = 0, /* fail if any missing objects are encountered */ + MA_ALLOW_ANY, /* silently allow ALL missing objects */ + MA_PRINT, /* print ALL missing objects in special section */ +}; +static enum missing_action arg_missing_action; + +#define DEFAULT_OIDSET_SIZE (16*1024) + static void finish_commit(struct commit *commit, void *data); static void show_commit(struct commit *commit, void *data) { @@ -178,11 +195,31 @@ static void finish_commit(struct commit *commit, void *data) free_commit_buffer(commit); } +static inline void finish_object__ma(struct object *obj) +{ + switch (arg_missing_action) { + case MA_ERROR: + die("missing blob object '%s'", oid_to_hex(&obj->oid)); + return; + + case MA_ALLOW_ANY: + return; + + case MA_PRINT: + oidset_insert(&missing_objects, &obj->oid); + return; + + default: + BUG("unhandled missing_action"); + return; + } +} + static void finish_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; if (obj->type == OBJ_BLOB && !has_object_file(&obj->oid)) - die("missing blob object '%s'", oid_to_hex(&obj->oid)); + finish_object__ma(obj); if (info->revs->verify_objects && !obj->parsed && obj->type != OBJ_COMMIT) parse_object(&obj->oid); } @@ -269,6 +306,26 @@ static int show_object_fast( return 1; } +static inline int parse_missing_action_value(const char *value) +{ + if (!strcmp(value, "error")) { + arg_missing_action = MA_ERROR; + return 1; + } + + if (!strcmp(value, "allow-any")) { + arg_missing_action = MA_ALLOW_ANY; + return 1; + } + + if (!strcmp(value, "print")) { + arg_missing_action = MA_PRINT; + return 1; + } + + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -335,6 +392,30 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) show_progress = arg; continue; } + + if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { + parse_list_objects_filter(&filter_options, arg); + if (filter_options.choice && !revs.blob_objects) + die(_("object filtering requires --objects")); + if (filter_options.choice == LOFC_SPARSE_OID && + !filter_options.sparse_oid_value) + die(_("invalid sparse value '%s'"), + filter_options.filter_spec); + continue; + } + if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { + list_objects_filter_release(&filter_options); + continue; + } + if (!strcmp(arg, "--filter-print-omitted")) { + arg_print_omitted = 1; + continue; + } + + if (skip_prefix(arg, "--missing=", &arg) && + parse_missing_action_value(arg)) + continue; + usage(rev_list_usage); } @@ -360,6 +441,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.show_notes) die(_("rev-list does not support display of notes")); + if (filter_options.choice && use_bitmap_index) + die(_("cannot combine --use-bitmap-index with object filtering")); + save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || revs.grep_filter.header_list); @@ -403,7 +487,31 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) return show_bisect_vars(&info, reaches, all); } - traverse_commit_list(&revs, show_commit, show_object, &info); + if (arg_print_omitted) + oidset_init(&omitted_objects, DEFAULT_OIDSET_SIZE); + if (arg_missing_action == MA_PRINT) + oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE); + + traverse_commit_list_filtered( + &filter_options, &revs, show_commit, show_object, &info, + (arg_print_omitted ? &omitted_objects : NULL)); + + if (arg_print_omitted) { + struct oidset_iter iter; + struct object_id *oid; + oidset_iter_init(&omitted_objects, &iter); + while ((oid = oidset_iter_next(&iter))) + printf("~%s\n", oid_to_hex(oid)); + oidset_clear(&omitted_objects); + } + if (arg_missing_action == MA_PRINT) { + struct oidset_iter iter; + struct object_id *oid; + oidset_iter_init(&missing_objects, &iter); + while ((oid = oidset_iter_next(&iter))) + printf("?%s\n", oid_to_hex(oid)); + oidset_clear(&missing_objects); + } stop_progress(&progress); diff --git a/builtin/rm.c b/builtin/rm.c index d91451fea1..4a2fcca27b 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -382,7 +382,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); if (gitmodules_modified) - stage_updated_gitmodules(); + stage_updated_gitmodules(&the_index); } if (active_cache_changed) { diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 2e24b5c330..e8a4aa40cb 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -1,6 +1,6 @@ #include "cache.h" #include "config.h" -#include "commit.h" +#include "pretty.h" #include "refs.h" #include "builtin.h" #include "color.h" diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2086f0eb08..a5c4a8a694 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -623,7 +623,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, if (refs_head_ref(get_submodule_ref_store(path), handle_submodule_head_ref, &oid)) - die(_("could not resolve HEAD ref inside the" + die(_("could not resolve HEAD ref inside the " "submodule '%s'"), path); print_status(flags, '+', path, &oid, displaypath); diff --git a/builtin/worktree.c b/builtin/worktree.c index 002a569a11..7cef5b120b 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -230,20 +230,21 @@ static int add_worktree(const char *path, const char *refname, int counter = 0, len, ret; struct strbuf symref = STRBUF_INIT; struct commit *commit = NULL; + int is_branch = 0; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && - ref_exists(symref.buf)) { /* it's a branch */ + ref_exists(symref.buf)) { + is_branch = 1; if (!opts->force) die_if_checked_out(symref.buf, 0); - } else { /* must be a commit */ - commit = lookup_commit_reference_by_name(refname); - if (!commit) - die(_("invalid reference: %s"), refname); } + commit = lookup_commit_reference_by_name(refname); + if (!commit) + die(_("invalid reference: %s"), refname); name = worktree_basename(path, &len); git_path_buf(&sb_repo, "worktrees/%.*s", (int)(path + len - name), name); @@ -308,7 +309,7 @@ static int add_worktree(const char *path, const char *refname, argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; - if (commit) + if (!is_branch) argv_array_pushl(&cp.args, "update-ref", "HEAD", oid_to_hex(&commit->object.oid), NULL); else @@ -339,6 +340,15 @@ done: strbuf_addf(&sb, "%s/locked", sb_repo.buf); unlink_or_warn(sb.buf); } + + /* + * Hook failure does not warrant worktree deletion, so run hook after + * is_junk is cleared, but do return appropriate code when hook fails. + */ + if (!ret && opts->checkout) + ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), "1", NULL); + argv_array_clear(&child_env); strbuf_release(&sb); strbuf_release(&symref); @@ -14,6 +14,7 @@ #include "hash.h" #include "path.h" #include "sha1-array.h" +#include "repository.h" #ifndef platform_SHA_CTX /* @@ -77,6 +78,8 @@ struct object_id { unsigned char hash[GIT_MAX_RAWSZ]; }; +#define the_hash_algo the_repository->hash_algo + #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT) #define DTYPE(de) ((de)->d_type) #else @@ -451,6 +454,16 @@ static inline enum object_type object_type(unsigned int mode) #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" /* + * Environment variable used in handshaking the wire protocol. + * Contains a colon ':' separated list of keys with optional values + * 'key[=value]'. Presence of unknown keys and values must be + * ignored. + */ +#define GIT_PROTOCOL_ENVIRONMENT "GIT_PROTOCOL" +/* HTTP header used to handshake the wire protocol */ +#define GIT_PROTOCOL_HEADER "Git-Protocol" + +/* * This environment variable is expected to contain a boolean indicating * whether we should or should not treat: * @@ -631,6 +644,15 @@ extern int write_locked_index(struct index_state *, struct lock_file *lock, unsi extern int discard_index(struct index_state *); extern void move_index_extensions(struct index_state *dst, struct index_state *src); extern int unmerged_index(const struct index_state *); + +/** + * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn + * branch, returns 1 if there are entries in the index, 0 otherwise. If an + * strbuf is provided, the space-separated list of files that differ will be + * appended to it. + */ +extern int index_has_changes(struct strbuf *sb); + extern int verify_path(const char *path); extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change); extern int index_dir_exists(struct index_state *istate, const char *name, int namelen); @@ -711,6 +733,7 @@ extern int ie_modified(struct index_state *, const struct cache_entry *, struct #define HASH_WRITE_OBJECT 1 #define HASH_FORMAT_CHECK 2 +#define HASH_RENORMALIZE 4 extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags); @@ -896,6 +919,7 @@ struct repository_format { int version; int precious_objects; int is_bare; + int hash_algo; char *work_tree; struct string_list unknown_extensions; }; @@ -1028,22 +1052,22 @@ extern const struct object_id empty_blob_oid; static inline int is_empty_blob_sha1(const unsigned char *sha1) { - return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN); + return !hashcmp(sha1, the_hash_algo->empty_blob->hash); } static inline int is_empty_blob_oid(const struct object_id *oid) { - return !hashcmp(oid->hash, EMPTY_BLOB_SHA1_BIN); + return !oidcmp(oid, the_hash_algo->empty_blob); } static inline int is_empty_tree_sha1(const unsigned char *sha1) { - return !hashcmp(sha1, EMPTY_TREE_SHA1_BIN); + return !hashcmp(sha1, the_hash_algo->empty_tree->hash); } static inline int is_empty_tree_oid(const struct object_id *oid) { - return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN); + return !oidcmp(oid, the_hash_algo->empty_tree); } /* set default permissions by passing mode arguments to open(2) */ @@ -1476,6 +1500,7 @@ extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); extern const char *git_pager(int stdout_is_tty); +extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); extern void reset_ident_date(void); @@ -1957,4 +1982,10 @@ void sleep_millisec(int millisec); */ void safe_create_dir(const char *dir, int share); +/* + * Should we print an ellipsis after an abbreviated SHA-1 value + * when doing diff-raw output or indicating a detached HEAD? + */ +extern int print_sha1_ellipsis(void); + #endif /* CACHE_H */ diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 5bd06fe900..75a9fd2475 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -8,10 +8,8 @@ P4WHENCE=http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION -case "${TRAVIS_OS_NAME:-linux}" in -linux) - export GIT_TEST_HTTPD=YesPlease - +case "$jobname" in +linux-clang|linux-gcc) mkdir --parents "$P4_PATH" pushd "$P4_PATH" wget --quiet "$P4WHENCE/bin.linux26x86_64/p4d" @@ -26,7 +24,7 @@ linux) cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs . popd ;; -osx) +osx-clang|osx-gcc) brew update --quiet # Uncomment this if you want to run perf tests: # brew install gnu-time diff --git a/ci/lib-travisci.sh b/ci/lib-travisci.sh index ac05f1f469..07f27c7270 100755 --- a/ci/lib-travisci.sh +++ b/ci/lib-travisci.sh @@ -16,21 +16,110 @@ skip_branch_tip_with_tag () { if TAG=$(git describe --exact-match "$TRAVIS_BRANCH" 2>/dev/null) && test "$TAG" != "$TRAVIS_BRANCH" then - echo "Tip of $TRAVIS_BRANCH is exactly at $TAG" + echo "$(tput setaf 2)Tip of $TRAVIS_BRANCH is exactly at $TAG$(tput sgr0)" exit 0 fi } +good_trees_file="$HOME/travis-cache/good-trees" + +# Save some info about the current commit's tree, so we can skip the build +# job if we encounter the same tree again and can provide a useful info +# message. +save_good_tree () { + echo "$(git rev-parse $TRAVIS_COMMIT^{tree}) $TRAVIS_COMMIT $TRAVIS_JOB_NUMBER $TRAVIS_JOB_ID" >>"$good_trees_file" + # limit the file size + tail -1000 "$good_trees_file" >"$good_trees_file".tmp + mv "$good_trees_file".tmp "$good_trees_file" +} + +# Skip the build job if the same tree has already been built and tested +# successfully before (e.g. because the branch got rebased, changing only +# the commit messages). +skip_good_tree () { + if ! good_tree_info="$(grep "^$(git rev-parse $TRAVIS_COMMIT^{tree}) " "$good_trees_file")" + then + # Haven't seen this tree yet, or no cached good trees file yet. + # Continue the build job. + return + fi + + echo "$good_tree_info" | { + read tree prev_good_commit prev_good_job_number prev_good_job_id + + if test "$TRAVIS_JOB_ID" = "$prev_good_job_id" + then + cat <<-EOF + $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) + This commit has already been built and tested successfully by this build job. + To force a re-build delete the branch's cache and then hit 'Restart job'. + EOF + else + cat <<-EOF + $(tput setaf 2)Skipping build job for commit $TRAVIS_COMMIT.$(tput sgr0) + This commit's tree has already been built and tested successfully in build job $prev_good_job_number for commit $prev_good_commit. + The log of that build job is available at https://travis-ci.org/$TRAVIS_REPO_SLUG/jobs/$prev_good_job_id + To force a re-build delete the branch's cache and then hit 'Restart job'. + EOF + fi + } + + exit 0 +} + +check_unignored_build_artifacts () +{ + ! git ls-files --other --exclude-standard --error-unmatch \ + -- ':/*' 2>/dev/null || + { + echo "$(tput setaf 1)error: found unignored build artifacts$(tput sgr0)" + false + } +} + # Set 'exit on error' for all CI scripts to let the caller know that -# something went wrong -set -e +# something went wrong. +# Set tracing executed commands, primarily setting environment variables +# and installing dependencies. +set -ex + +mkdir -p "$HOME/travis-cache" skip_branch_tip_with_tag +skip_good_tree + +if test -z "$jobname" +then + jobname="$TRAVIS_OS_NAME-$CC" +fi + +export DEVELOPER=1 +export DEFAULT_TEST_TARGET=prove +export GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" +export GIT_TEST_OPTS="--verbose-log" +export GIT_TEST_CLONE_2GB=YesPlease -case "${TRAVIS_OS_NAME:-linux}" in -linux) - P4_PATH="$(pwd)/custom/p4" - GIT_LFS_PATH="$(pwd)/custom/git-lfs" +case "$jobname" in +linux-clang|linux-gcc) + export GIT_TEST_HTTPD=YesPlease + + # The Linux build installs the defined dependency versions below. + # The OS X build installs the latest available versions. Keep that + # in mind when you encounter a broken OS X build! + export LINUX_P4_VERSION="16.2" + export LINUX_GIT_LFS_VERSION="1.5.2" + + P4_PATH="$HOME/custom/p4" + GIT_LFS_PATH="$HOME/custom/git-lfs" export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH" ;; +osx-clang|osx-gcc) + # t9810 occasionally fails on Travis CI OS X + # t9816 occasionally fails with "TAP out of sequence errors" on + # Travis CI OS X + export GIT_SKIP_TESTS="t9810 t9816" + ;; +GETTEXT_POISON) + export GETTEXT_POISON=YesPlease + ;; esac diff --git a/ci/print-test-failures.sh b/ci/print-test-failures.sh index 8c8973cbf3..4f261ddc01 100755 --- a/ci/print-test-failures.sh +++ b/ci/print-test-failures.sh @@ -5,6 +5,15 @@ . ${0%/*}/lib-travisci.sh +# Tracing executed commands would produce too much noise in the loop below. +set +x + +if ! ls t/test-results/*.exit >/dev/null 2>/dev/null +then + echo "Build job failed before the tests could have been run" + exit +fi + for TEST_EXIT in t/test-results/*.exit do if [ "$(cat "$TEST_EXIT")" != "0" ] diff --git a/ci/run-linux32-build.sh b/ci/run-linux32-build.sh index e30fb2cddc..c19c50c1c9 100755 --- a/ci/run-linux32-build.sh +++ b/ci/run-linux32-build.sh @@ -6,6 +6,8 @@ # run-linux32-build.sh [host-user-id] # +set -x + # Update packages to the latest available versions linux32 --32bit i386 sh -c ' apt update >/dev/null && @@ -25,6 +27,7 @@ test -z $HOST_UID || (CI_USER="ci" && useradd -u $HOST_UID $CI_USER) && # Build and test linux32 --32bit i386 su -m -l $CI_USER -c ' cd /usr/src/git && + ln -s /tmp/travis-cache/.prove t/.prove && make --jobs=2 && make --quiet test ' diff --git a/ci/run-linux32-docker.sh b/ci/run-linux32-docker.sh index 0edf63acfa..4f191c5bb1 100755 --- a/ci/run-linux32-docker.sh +++ b/ci/run-linux32-docker.sh @@ -19,5 +19,10 @@ docker run \ --env GIT_TEST_OPTS \ --env GIT_TEST_CLONE_2GB \ --volume "${PWD}:/usr/src/git" \ + --volume "${HOME}/travis-cache:/tmp/travis-cache" \ daald/ubuntu32:xenial \ /usr/src/git/ci/run-linux32-build.sh $(id -u $USER) + +check_unignored_build_artifacts + +save_good_tree diff --git a/ci/run-static-analysis.sh b/ci/run-static-analysis.sh index 68dd0f080e..fe4ee4e06b 100755 --- a/ci/run-static-analysis.sh +++ b/ci/run-static-analysis.sh @@ -6,3 +6,5 @@ . ${0%/*}/lib-travisci.sh make coccicheck + +save_good_tree diff --git a/ci/run-tests.sh b/ci/run-tests.sh index f0c743de94..22355f0091 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -5,6 +5,9 @@ . ${0%/*}/lib-travisci.sh -mkdir -p $HOME/travis-cache ln -s $HOME/travis-cache/.prove t/.prove make --quiet test + +check_unignored_build_artifacts + +save_good_tree diff --git a/ci/run-windows-build.sh b/ci/run-windows-build.sh index 8757b3a97c..d99a180e52 100755 --- a/ci/run-windows-build.sh +++ b/ci/run-windows-build.sh @@ -69,6 +69,10 @@ esac echo "Visual Studio Team Services Build #${BUILD_ID}" +# Tracing execued commands would produce too much noise in the waiting +# loop below. +set +x + # Wait until build job finished STATUS= RESULT= @@ -90,7 +94,10 @@ done # Print log echo "" echo "" +set -x gfwci "action=log&buildId=$BUILD_ID" | cut -c 30- # Set exit code for TravisCI test "$RESULT" = "success" + +save_good_tree diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh index 7a0a848e83..a20de9ca12 100755 --- a/ci/test-documentation.sh +++ b/ci/test-documentation.sh @@ -18,6 +18,9 @@ test -s Documentation/git.xml test -s Documentation/git.1 grep '<meta name="generator" content="AsciiDoc ' Documentation/git.html +rm -f stdout.log stderr.log +check_unignored_build_artifacts + # Build docs with AsciiDoctor make clean make --jobs=2 USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.log >&2) @@ -25,3 +28,8 @@ sed '/^GIT_VERSION = / d' stderr.log ! test -s stderr.log test -s Documentation/git.html grep '<meta name="generator" content="Asciidoctor ' Documentation/git.html + +rm -f stdout.log stderr.log +check_unignored_build_artifacts + +save_good_tree @@ -329,8 +329,7 @@ static int check_auto_color(void) if (color_stdout_is_tty < 0) color_stdout_is_tty = isatty(1); if (color_stdout_is_tty || (pager_in_use() && pager_use_color)) { - char *term = getenv("TERM"); - if (term && strcmp(term, "dumb")) + if (!is_terminal_dumb()) return 1; } return 0; @@ -7,6 +7,7 @@ #include "decorate.h" #include "gpg-interface.h" #include "string-list.h" +#include "pretty.h" struct commit_list { struct commit *item; @@ -121,93 +122,13 @@ struct commit_list *copy_commit_list(struct commit_list *list); void free_commit_list(struct commit_list *list); -/* Commit formats */ -enum cmit_fmt { - CMIT_FMT_RAW, - CMIT_FMT_MEDIUM, - CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, - CMIT_FMT_SHORT, - CMIT_FMT_FULL, - CMIT_FMT_FULLER, - CMIT_FMT_ONELINE, - CMIT_FMT_EMAIL, - CMIT_FMT_MBOXRD, - CMIT_FMT_USERFORMAT, - - CMIT_FMT_UNSPECIFIED -}; - -static inline int cmit_fmt_is_mail(enum cmit_fmt fmt) -{ - return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD); -} - struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ -struct pretty_print_context { - /* - * Callers should tweak these to change the behavior of pp_* functions. - */ - enum cmit_fmt fmt; - int abbrev; - const char *after_subject; - int preserve_subject; - struct date_mode date_mode; - unsigned date_mode_explicit:1; - int print_email_subject; - int expand_tabs_in_log; - int need_8bit_cte; - char *notes_message; - struct reflog_walk_info *reflog_info; - struct rev_info *rev; - const char *output_encoding; - struct string_list *mailmap; - int color; - struct ident_split *from_ident; - - /* - * Fields below here are manipulated internally by pp_* functions and - * should not be counted on by callers. - */ - struct string_list in_body_headers; - int graph_width; -}; - -struct userformat_want { - unsigned notes:1; -}; - extern int has_non_ascii(const char *text); extern const char *logmsg_reencode(const struct commit *commit, char **commit_encoding, const char *output_encoding); -extern void get_commit_format(const char *arg, struct rev_info *); -extern const char *format_subject(struct strbuf *sb, const char *msg, - const char *line_separator); -extern void userformat_find_requirements(const char *fmt, struct userformat_want *w); -extern int commit_format_is_empty(enum cmit_fmt); extern const char *skip_blank_lines(const char *msg); -extern void format_commit_message(const struct commit *commit, - const char *format, struct strbuf *sb, - const struct pretty_print_context *context); -extern void pretty_print_commit(struct pretty_print_context *pp, - const struct commit *commit, - struct strbuf *sb); -extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, - struct strbuf *sb); -void pp_user_info(struct pretty_print_context *pp, - const char *what, struct strbuf *sb, - const char *line, const char *encoding); -void pp_title_line(struct pretty_print_context *pp, - const char **msg_p, - struct strbuf *sb, - const char *encoding, - int need_8bit_cte); -void pp_remainder(struct pretty_print_context *pp, - const char **msg_p, - struct strbuf *sb, - int indent); - /** Removes the first commit from a list sorted by date, and adds all * of its parents. @@ -990,6 +990,16 @@ int git_config_pathname(const char **dest, const char *var, const char *value) return 0; } +int git_config_expiry_date(timestamp_t *timestamp, const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + if (parse_expiry_date(value, timestamp)) + return error(_("'%s' for '%s' is not a valid timestamp"), + value, var); + return 0; +} + static int git_default_core_config(const char *var, const char *value) { /* This needs a better name */ @@ -2329,7 +2339,7 @@ static ssize_t write_section(int fd, const char *key) struct strbuf sb = store_create_section(key); ssize_t ret; - ret = write_in_full(fd, sb.buf, sb.len) == sb.len; + ret = write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); return ret; @@ -2824,7 +2834,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename * multiple [branch "$name"] sections. */ if (copystr.len > 0) { - if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) { + if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) { ret = write_error(get_lock_file_path(&lock)); goto out; } @@ -2886,7 +2896,7 @@ static int git_config_copy_or_rename_section_in_file(const char *config_filename * logic in the loop above. */ if (copystr.len > 0) { - if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) { + if (write_in_full(out_fd, copystr.buf, copystr.len) < 0) { ret = write_error(get_lock_file_path(&lock)); goto out; } @@ -58,6 +58,7 @@ extern int git_config_bool_or_int(const char *, const char *, int *); extern int git_config_bool(const char *, const char *); extern int git_config_string(const char **, const char *, const char *); extern int git_config_pathname(const char **, const char *, const char *); +extern int git_config_expiry_date(timestamp_t *, const char *, const char *); extern int git_config_set_in_file_gently(const char *, const char *, const char *); extern void git_config_set_in_file(const char *, const char *, const char *); extern int git_config_set_gently(const char *, const char *); diff --git a/configure.ac b/configure.ac index 2f55237e65..7f8415140f 100644 --- a/configure.ac +++ b/configure.ac @@ -241,7 +241,6 @@ AC_MSG_NOTICE([CHECKS for site configuration]) # a bundled SHA1 routine optimized for PowerPC. # # Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies BLK_SHA1. # # Define OPENSSLDIR=/foo/bar if your openssl header and library files are in # /foo/bar/include and /foo/bar/lib directories. @@ -11,6 +11,8 @@ #include "string-list.h" #include "sha1-array.h" #include "transport.h" +#include "strbuf.h" +#include "protocol.h" static char *server_capabilities; static const char *parse_feature_value(const char *, const char *, int *); @@ -108,6 +110,118 @@ static void annotate_refs_with_symref_info(struct ref *ref) } /* + * Read one line of a server's ref advertisement into packet_buffer. + */ +static int read_remote_ref(int in, char **src_buf, size_t *src_len, + int *responded) +{ + int len = packet_read(in, src_buf, src_len, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + const char *arg; + if (len < 0) + die_initial_contact(*responded); + if (len > 4 && skip_prefix(packet_buffer, "ERR ", &arg)) + die("remote error: %s", arg); + + *responded = 1; + + return len; +} + +#define EXPECTING_PROTOCOL_VERSION 0 +#define EXPECTING_FIRST_REF 1 +#define EXPECTING_REF 2 +#define EXPECTING_SHALLOW 3 + +/* Returns 1 if packet_buffer is a protocol version pkt-line, 0 otherwise. */ +static int process_protocol_version(void) +{ + switch (determine_protocol_version_client(packet_buffer)) { + case protocol_v1: + return 1; + case protocol_v0: + return 0; + default: + die("server is speaking an unknown protocol"); + } +} + +static void process_capabilities(int *len) +{ + int nul_location = strlen(packet_buffer); + if (nul_location == *len) + return; + server_capabilities = xstrdup(packet_buffer + nul_location + 1); + *len = nul_location; +} + +static int process_dummy_ref(void) +{ + struct object_id oid; + const char *name; + + if (parse_oid_hex(packet_buffer, &oid, &name)) + return 0; + if (*name != ' ') + return 0; + name++; + + return !oidcmp(&null_oid, &oid) && !strcmp(name, "capabilities^{}"); +} + +static void check_no_capabilities(int len) +{ + if (strlen(packet_buffer) != len) + warning("Ignoring capabilities after first line '%s'", + packet_buffer + strlen(packet_buffer)); +} + +static int process_ref(int len, struct ref ***list, unsigned int flags, + struct oid_array *extra_have) +{ + struct object_id old_oid; + const char *name; + + if (parse_oid_hex(packet_buffer, &old_oid, &name)) + return 0; + if (*name != ' ') + return 0; + name++; + + if (extra_have && !strcmp(name, ".have")) { + oid_array_append(extra_have, &old_oid); + } else if (!strcmp(name, "capabilities^{}")) { + die("protocol error: unexpected capabilities^{}"); + } else if (check_ref(name, flags)) { + struct ref *ref = alloc_ref(name); + oidcpy(&ref->old_oid, &old_oid); + **list = ref; + *list = &ref->next; + } + check_no_capabilities(len); + return 1; +} + +static int process_shallow(int len, struct oid_array *shallow_points) +{ + const char *arg; + struct object_id old_oid; + + if (!skip_prefix(packet_buffer, "shallow ", &arg)) + return 0; + + if (get_oid_hex(arg, &old_oid)) + die("protocol error: expected shallow sha-1, got '%s'", arg); + if (!shallow_points) + die("repository on the other end cannot be shallow"); + oid_array_append(shallow_points, &old_oid); + check_no_capabilities(len); + return 1; +} + +/* * Read all the refs from the other end */ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, @@ -123,76 +237,41 @@ struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, * willing to talk to us. A hang-up before seeing any * response does not necessarily mean an ACL problem, though. */ - int saw_response; - int got_dummy_ref_with_capabilities_declaration = 0; + int responded = 0; + int len; + int state = EXPECTING_PROTOCOL_VERSION; *list = NULL; - for (saw_response = 0; ; saw_response = 1) { - struct ref *ref; - struct object_id old_oid; - char *name; - int len, name_len; - char *buffer = packet_buffer; - const char *arg; - - len = packet_read(in, &src_buf, &src_len, - packet_buffer, sizeof(packet_buffer), - PACKET_READ_GENTLE_ON_EOF | - PACKET_READ_CHOMP_NEWLINE); - if (len < 0) - die_initial_contact(saw_response); - - if (!len) - break; - - if (len > 4 && skip_prefix(buffer, "ERR ", &arg)) - die("remote error: %s", arg); - - if (len == GIT_SHA1_HEXSZ + strlen("shallow ") && - skip_prefix(buffer, "shallow ", &arg)) { - if (get_oid_hex(arg, &old_oid)) - die("protocol error: expected shallow sha-1, got '%s'", arg); - if (!shallow_points) - die("repository on the other end cannot be shallow"); - oid_array_append(shallow_points, &old_oid); - continue; - } - - if (len < GIT_SHA1_HEXSZ + 2 || get_oid_hex(buffer, &old_oid) || - buffer[GIT_SHA1_HEXSZ] != ' ') - die("protocol error: expected sha/ref, got '%s'", buffer); - name = buffer + GIT_SHA1_HEXSZ + 1; - - name_len = strlen(name); - if (len != name_len + GIT_SHA1_HEXSZ + 1) { - free(server_capabilities); - server_capabilities = xstrdup(name + name_len + 1); - } - if (extra_have && !strcmp(name, ".have")) { - oid_array_append(extra_have, &old_oid); - continue; - } - - if (!strcmp(name, "capabilities^{}")) { - if (saw_response) - die("protocol error: unexpected capabilities^{}"); - if (got_dummy_ref_with_capabilities_declaration) - die("protocol error: multiple capabilities^{}"); - got_dummy_ref_with_capabilities_declaration = 1; - continue; + while ((len = read_remote_ref(in, &src_buf, &src_len, &responded))) { + switch (state) { + case EXPECTING_PROTOCOL_VERSION: + if (process_protocol_version()) { + state = EXPECTING_FIRST_REF; + break; + } + state = EXPECTING_FIRST_REF; + /* fallthrough */ + case EXPECTING_FIRST_REF: + process_capabilities(&len); + if (process_dummy_ref()) { + state = EXPECTING_SHALLOW; + break; + } + state = EXPECTING_REF; + /* fallthrough */ + case EXPECTING_REF: + if (process_ref(len, &list, flags, extra_have)) + break; + state = EXPECTING_SHALLOW; + /* fallthrough */ + case EXPECTING_SHALLOW: + if (process_shallow(len, shallow_points)) + break; + die("protocol error: unexpected '%s'", packet_buffer); + default: + die("unexpected state %d", state); } - - if (!check_ref(name, flags)) - continue; - - if (got_dummy_ref_with_capabilities_declaration) - die("protocol error: unexpected ref after capabilities^{}"); - - ref = alloc_ref(buffer + GIT_SHA1_HEXSZ + 1); - oidcpy(&ref->old_oid, &old_oid); - *list = ref; - list = &ref->next; } annotate_refs_with_symref_info(*orig_list); @@ -503,12 +582,25 @@ static int git_tcp_connect_sock(char *host, int flags) #endif /* NO_IPV6 */ -static void git_tcp_connect(int fd[2], char *host, int flags) +/* + * Dummy child_process returned by git_connect() if the transport protocol + * does not need fork(2). + */ +static struct child_process no_fork = CHILD_PROCESS_INIT; + +int git_connection_is_socket(struct child_process *conn) +{ + return conn == &no_fork; +} + +static struct child_process *git_tcp_connect(int fd[2], char *host, int flags) { int sockfd = git_tcp_connect_sock(host, flags); fd[0] = sockfd; fd[1] = dup(sockfd); + + return &no_fork; } @@ -682,8 +774,6 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, return protocol; } -static struct child_process no_fork = CHILD_PROCESS_INIT; - static const char *get_ssh_command(void) { const char *ssh; @@ -697,37 +787,47 @@ static const char *get_ssh_command(void) return NULL; } -static int override_ssh_variant(int *port_option, int *needs_batch) +enum ssh_variant { + VARIANT_AUTO, + VARIANT_SIMPLE, + VARIANT_SSH, + VARIANT_PLINK, + VARIANT_PUTTY, + VARIANT_TORTOISEPLINK, +}; + +static void override_ssh_variant(enum ssh_variant *ssh_variant) { - char *variant; + const char *variant = getenv("GIT_SSH_VARIANT"); - variant = xstrdup_or_null(getenv("GIT_SSH_VARIANT")); - if (!variant && - git_config_get_string("ssh.variant", &variant)) - return 0; + if (!variant && git_config_get_string_const("ssh.variant", &variant)) + return; - if (!strcmp(variant, "plink") || !strcmp(variant, "putty")) { - *port_option = 'P'; - *needs_batch = 0; - } else if (!strcmp(variant, "tortoiseplink")) { - *port_option = 'P'; - *needs_batch = 1; - } else { - *port_option = 'p'; - *needs_batch = 0; - } - free(variant); - return 1; + if (!strcmp(variant, "auto")) + *ssh_variant = VARIANT_AUTO; + else if (!strcmp(variant, "plink")) + *ssh_variant = VARIANT_PLINK; + else if (!strcmp(variant, "putty")) + *ssh_variant = VARIANT_PUTTY; + else if (!strcmp(variant, "tortoiseplink")) + *ssh_variant = VARIANT_TORTOISEPLINK; + else if (!strcmp(variant, "simple")) + *ssh_variant = VARIANT_SIMPLE; + else + *ssh_variant = VARIANT_SSH; } -static void handle_ssh_variant(const char *ssh_command, int is_cmdline, - int *port_option, int *needs_batch) +static enum ssh_variant determine_ssh_variant(const char *ssh_command, + int is_cmdline) { + enum ssh_variant ssh_variant = VARIANT_AUTO; const char *variant; char *p = NULL; - if (override_ssh_variant(port_option, needs_batch)) - return; + override_ssh_variant(&ssh_variant); + + if (ssh_variant != VARIANT_AUTO) + return ssh_variant; if (!is_cmdline) { p = xstrdup(ssh_command); @@ -746,27 +846,200 @@ static void handle_ssh_variant(const char *ssh_command, int is_cmdline, free(ssh_argv); } else { free(p); - return; + return ssh_variant; } } - if (!strcasecmp(variant, "plink") || - !strcasecmp(variant, "plink.exe")) - *port_option = 'P'; + if (!strcasecmp(variant, "ssh") || + !strcasecmp(variant, "ssh.exe")) + ssh_variant = VARIANT_SSH; + else if (!strcasecmp(variant, "plink") || + !strcasecmp(variant, "plink.exe")) + ssh_variant = VARIANT_PLINK; else if (!strcasecmp(variant, "tortoiseplink") || - !strcasecmp(variant, "tortoiseplink.exe")) { - *port_option = 'P'; - *needs_batch = 1; - } + !strcasecmp(variant, "tortoiseplink.exe")) + ssh_variant = VARIANT_TORTOISEPLINK; + free(p); + return ssh_variant; +} + +/* + * Open a connection using Git's native protocol. + * + * The caller is responsible for freeing hostandport, but this function may + * modify it (for example, to truncate it to remove the port part). + */ +static struct child_process *git_connect_git(int fd[2], char *hostandport, + const char *path, const char *prog, + int flags) +{ + struct child_process *conn; + struct strbuf request = STRBUF_INIT; + /* + * Set up virtual host information based on where we will + * connect, unless the user has overridden us in + * the environment. + */ + char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST"); + if (target_host) + target_host = xstrdup(target_host); + else + target_host = xstrdup(hostandport); + + transport_check_allowed("git"); + + /* + * These underlying connection commands die() if they + * cannot connect. + */ + if (git_use_proxy(hostandport)) + conn = git_proxy_connect(fd, hostandport); + else + conn = git_tcp_connect(fd, hostandport, flags); + /* + * Separate original protocol components prog and path + * from extended host header with a NUL byte. + * + * Note: Do not add any other headers here! Doing so + * will cause older git-daemon servers to crash. + */ + strbuf_addf(&request, + "%s %s%chost=%s%c", + prog, path, 0, + target_host, 0); + + /* If using a new version put that stuff here after a second null byte */ + if (get_protocol_version_config() > 0) { + strbuf_addch(&request, '\0'); + strbuf_addf(&request, "version=%d%c", + get_protocol_version_config(), '\0'); + } + + packet_write(fd[1], request.buf, request.len); + + free(target_host); + strbuf_release(&request); + return conn; +} + +/* + * Append the appropriate environment variables to `env` and options to + * `args` for running ssh in Git's SSH-tunneled transport. + */ +static void push_ssh_options(struct argv_array *args, struct argv_array *env, + enum ssh_variant variant, const char *port, + int flags) +{ + if (variant == VARIANT_SSH && + get_protocol_version_config() > 0) { + argv_array_push(args, "-o"); + argv_array_push(args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT); + argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=version=%d", + get_protocol_version_config()); + } + + if (flags & CONNECT_IPV4) { + switch (variant) { + case VARIANT_AUTO: + BUG("VARIANT_AUTO passed to push_ssh_options"); + case VARIANT_SIMPLE: + die("ssh variant 'simple' does not support -4"); + case VARIANT_SSH: + case VARIANT_PLINK: + case VARIANT_PUTTY: + case VARIANT_TORTOISEPLINK: + argv_array_push(args, "-4"); + } + } else if (flags & CONNECT_IPV6) { + switch (variant) { + case VARIANT_AUTO: + BUG("VARIANT_AUTO passed to push_ssh_options"); + case VARIANT_SIMPLE: + die("ssh variant 'simple' does not support -6"); + case VARIANT_SSH: + case VARIANT_PLINK: + case VARIANT_PUTTY: + case VARIANT_TORTOISEPLINK: + argv_array_push(args, "-6"); + } + } + + if (variant == VARIANT_TORTOISEPLINK) + argv_array_push(args, "-batch"); + + if (port) { + switch (variant) { + case VARIANT_AUTO: + BUG("VARIANT_AUTO passed to push_ssh_options"); + case VARIANT_SIMPLE: + die("ssh variant 'simple' does not support setting port"); + case VARIANT_SSH: + argv_array_push(args, "-p"); + break; + case VARIANT_PLINK: + case VARIANT_PUTTY: + case VARIANT_TORTOISEPLINK: + argv_array_push(args, "-P"); + } + + argv_array_push(args, port); + } +} + +/* Prepare a child_process for use by Git's SSH-tunneled transport. */ +static void fill_ssh_args(struct child_process *conn, const char *ssh_host, + const char *port, int flags) +{ + const char *ssh; + enum ssh_variant variant; + + if (looks_like_command_line_option(ssh_host)) + die("strange hostname '%s' blocked", ssh_host); + + ssh = get_ssh_command(); + if (ssh) { + variant = determine_ssh_variant(ssh, 1); + } else { + /* + * GIT_SSH is the no-shell version of + * GIT_SSH_COMMAND (and must remain so for + * historical compatibility). + */ + conn->use_shell = 0; + + ssh = getenv("GIT_SSH"); + if (!ssh) + ssh = "ssh"; + variant = determine_ssh_variant(ssh, 0); + } + + if (variant == VARIANT_AUTO) { + struct child_process detect = CHILD_PROCESS_INIT; + + detect.use_shell = conn->use_shell; + detect.no_stdin = detect.no_stdout = detect.no_stderr = 1; + + argv_array_push(&detect.args, ssh); + argv_array_push(&detect.args, "-G"); + push_ssh_options(&detect.args, &detect.env_array, + VARIANT_SSH, port, flags); + argv_array_push(&detect.args, ssh_host); + + variant = run_command(&detect) ? VARIANT_SIMPLE : VARIANT_SSH; + } + + argv_array_push(&conn->args, ssh); + push_ssh_options(&conn->args, &conn->env_array, variant, port, flags); + argv_array_push(&conn->args, ssh_host); } /* - * This returns a dummy child_process if the transport protocol does not - * need fork(2), or a struct child_process object if it does. Once done, - * finish the connection with finish_connect() with the value returned from - * this function (it is safe to call finish_connect() with NULL to support - * the former case). + * This returns the dummy child_process `no_fork` if the transport protocol + * does not need fork(2), or a struct child_process object if it does. Once + * done, finish the connection with finish_connect() with the value returned + * from this function (it is safe to call finish_connect() with NULL to + * support the former case). * * If it returns, the connect is successful; it just dies on errors (this * will hopefully be changed in a libification effort, to return NULL when @@ -776,7 +1049,7 @@ struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags) { char *hostandport, *path; - struct child_process *conn = &no_fork; + struct child_process *conn; enum protocol protocol; /* Without this we cannot rely on waitpid() to tell @@ -792,40 +1065,10 @@ struct child_process *git_connect(int fd[2], const char *url, printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; } else if (protocol == PROTO_GIT) { - /* - * Set up virtual host information based on where we will - * connect, unless the user has overridden us in - * the environment. - */ - char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST"); - if (target_host) - target_host = xstrdup(target_host); - else - target_host = xstrdup(hostandport); - - transport_check_allowed("git"); - - /* These underlying connection commands die() if they - * cannot connect. - */ - if (git_use_proxy(hostandport)) - conn = git_proxy_connect(fd, hostandport); - else - git_tcp_connect(fd, hostandport, flags); - /* - * Separate original protocol components prog and path - * from extended host header with a NUL byte. - * - * Note: Do not add any other headers here! Doing so - * will cause older git-daemon servers to crash. - */ - packet_write_fmt(fd[1], - "%s %s%chost=%s%c", - prog, path, 0, - target_host, 0); - free(target_host); + conn = git_connect_git(fd, hostandport, path, prog, flags); } else { struct strbuf cmd = STRBUF_INIT; + const char *const *var; conn = xmalloc(sizeof(*conn)); child_process_init(conn); @@ -838,13 +1081,12 @@ struct child_process *git_connect(int fd[2], const char *url, sq_quote_buf(&cmd, path); /* remove repo-local variables from the environment */ - conn->env = local_repo_env; + for (var = local_repo_env; *var; var++) + argv_array_push(&conn->env_array, *var); + conn->use_shell = 1; conn->in = conn->out = -1; if (protocol == PROTO_SSH) { - const char *ssh; - int needs_batch = 0; - int port_option = 'p'; char *ssh_host = hostandport; const char *port = NULL; transport_check_allowed("ssh"); @@ -866,46 +1108,13 @@ struct child_process *git_connect(int fd[2], const char *url, strbuf_release(&cmd); return NULL; } - - if (looks_like_command_line_option(ssh_host)) - die("strange hostname '%s' blocked", ssh_host); - - ssh = get_ssh_command(); - if (ssh) - handle_ssh_variant(ssh, 1, &port_option, - &needs_batch); - else { - /* - * GIT_SSH is the no-shell version of - * GIT_SSH_COMMAND (and must remain so for - * historical compatibility). - */ - conn->use_shell = 0; - - ssh = getenv("GIT_SSH"); - if (!ssh) - ssh = "ssh"; - else - handle_ssh_variant(ssh, 0, - &port_option, - &needs_batch); - } - - argv_array_push(&conn->args, ssh); - if (flags & CONNECT_IPV4) - argv_array_push(&conn->args, "-4"); - else if (flags & CONNECT_IPV6) - argv_array_push(&conn->args, "-6"); - if (needs_batch) - argv_array_push(&conn->args, "-batch"); - if (port) { - argv_array_pushf(&conn->args, - "-%c", port_option); - argv_array_push(&conn->args, port); - } - argv_array_push(&conn->args, ssh_host); + fill_ssh_args(conn, ssh_host, port, flags); } else { transport_check_allowed("file"); + if (get_protocol_version_config() > 0) { + argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d", + get_protocol_version_config()); + } } argv_array_push(&conn->args, cmd.buf); @@ -921,11 +1130,6 @@ struct child_process *git_connect(int fd[2], const char *url, return conn; } -int git_connection_is_socket(struct child_process *conn) -{ - return conn == &no_fork; -} - int finish_connect(struct child_process *conn) { int code; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index f07f16b28f..3683c772c5 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1204,7 +1204,7 @@ _git_branch () --color --no-color --verbose --abbrev= --no-abbrev --track --no-track --contains --no-contains --merged --no-merged --set-upstream-to= --edit-description --list - --unset-upstream --delete --move --remotes + --unset-upstream --delete --move --copy --remotes --column --no-column --sort= --points-at " ;; @@ -1400,7 +1400,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check --full-index --binary --abbrev --diff-filter= - --find-copies-harder + --find-copies-harder --ignore-cr-at-eol --text --ignore-space-at-eol --ignore-space-change --ignore-all-space --ignore-blank-lines --exit-code --quiet --ext-diff --no-ext-diff @@ -1922,6 +1922,7 @@ _git_pull () --*) __gitcomp " --rebase --no-rebase + --autostash --no-autostash $__git_merge_options $__git_fetch_options " @@ -2641,6 +2642,7 @@ _git_config () sendemail.suppressfrom sendemail.thread sendemail.to + sendemail.tocmd sendemail.validate sendemail.smtpbatchsize sendemail.smtprelogindelay diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index c6cbef38c2..983e419d2b 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -278,11 +278,12 @@ __git_ps1_colorize_gitstring () r="$c_clear$r" } +# Helper function to read the first line of a file into a variable. +# __git_eread requires 2 arguments, the file path and the name of the +# variable, in that order. __git_eread () { - local f="$1" - shift - test -r "$f" && read "$@" <"$f" + test -r "$1" && IFS=$'\r\n' read "$2" <"$1" } # __git_ps1 accepts 0 or 1 arguments (i.e., format string) @@ -220,18 +220,27 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action, } } -static int has_cr_in_index(const struct index_state *istate, const char *path) +static int has_crlf_in_index(const struct index_state *istate, const char *path) { unsigned long sz; void *data; - int has_cr; + const char *crp; + int has_crlf = 0; data = read_blob_data_from_index(istate, path, &sz); if (!data) return 0; - has_cr = memchr(data, '\r', sz) != NULL; + + crp = memchr(data, '\r', sz); + if (crp) { + unsigned int ret_stats; + ret_stats = gather_convert_stats(data, sz); + if (!(ret_stats & CONVERT_STAT_BITS_BIN) && + (ret_stats & CONVERT_STAT_BITS_TXT_CRLF)) + has_crlf = 1; + } free(data); - return has_cr; + return has_crlf; } static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats, @@ -290,7 +299,7 @@ static int crlf_to_git(const struct index_state *istate, * cherry-pick. */ if ((checksafe != SAFE_CRLF_RENORMALIZE) && - has_cr_in_index(istate, path)) + has_crlf_in_index(istate, path)) convert_crlf_into_lf = 0; } if ((checksafe == SAFE_CRLF_WARN || @@ -282,7 +282,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) return NULL; /* Fallthrough. Deny by default */ } -typedef int (*daemon_service_fn)(void); +typedef int (*daemon_service_fn)(const struct argv_array *env); struct daemon_service { const char *name; const char *config_name; @@ -363,7 +363,7 @@ error_return: } static int run_service(const char *dir, struct daemon_service *service, - struct hostinfo *hi) + struct hostinfo *hi, const struct argv_array *env) { const char *path; int enabled = service->enabled; @@ -422,7 +422,7 @@ static int run_service(const char *dir, struct daemon_service *service, */ signal(SIGTERM, SIG_IGN); - return service->fn(); + return service->fn(env); } static void copy_to_log(int fd) @@ -462,25 +462,34 @@ static int run_service_command(struct child_process *cld) return finish_command(cld); } -static int upload_pack(void) +static int upload_pack(const struct argv_array *env) { struct child_process cld = CHILD_PROCESS_INIT; argv_array_pushl(&cld.args, "upload-pack", "--strict", NULL); argv_array_pushf(&cld.args, "--timeout=%u", timeout); + + argv_array_pushv(&cld.env_array, env->argv); + return run_service_command(&cld); } -static int upload_archive(void) +static int upload_archive(const struct argv_array *env) { struct child_process cld = CHILD_PROCESS_INIT; argv_array_push(&cld.args, "upload-archive"); + + argv_array_pushv(&cld.env_array, env->argv); + return run_service_command(&cld); } -static int receive_pack(void) +static int receive_pack(const struct argv_array *env) { struct child_process cld = CHILD_PROCESS_INIT; argv_array_push(&cld.args, "receive-pack"); + + argv_array_pushv(&cld.env_array, env->argv); + return run_service_command(&cld); } @@ -573,8 +582,11 @@ static void canonicalize_client(struct strbuf *out, const char *in) /* * Read the host as supplied by the client connection. + * + * Returns a pointer to the character after the NUL byte terminating the host + * arguemnt, or 'extra_args' if there is no host arguemnt. */ -static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen) +static char *parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen) { char *val; int vallen; @@ -602,6 +614,43 @@ static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen) if (extra_args < end && *extra_args) die("Invalid request"); } + + return extra_args; +} + +static void parse_extra_args(struct hostinfo *hi, struct argv_array *env, + char *extra_args, int buflen) +{ + const char *end = extra_args + buflen; + struct strbuf git_protocol = STRBUF_INIT; + + /* First look for the host argument */ + extra_args = parse_host_arg(hi, extra_args, buflen); + + /* Look for additional arguments places after a second NUL byte */ + for (; extra_args < end; extra_args += strlen(extra_args) + 1) { + const char *arg = extra_args; + + /* + * Parse the extra arguments, adding most to 'git_protocol' + * which will be used to set the 'GIT_PROTOCOL' envvar in the + * service that will be run. + * + * If there ends up being a particular arg in the future that + * git-daemon needs to parse specificly (like the 'host' arg) + * then it can be parsed here and not added to 'git_protocol'. + */ + if (*arg) { + if (git_protocol.len > 0) + strbuf_addch(&git_protocol, ':'); + strbuf_addstr(&git_protocol, arg); + } + } + + if (git_protocol.len > 0) + argv_array_pushf(env, GIT_PROTOCOL_ENVIRONMENT "=%s", + git_protocol.buf); + strbuf_release(&git_protocol); } /* @@ -695,6 +744,7 @@ static int execute(void) int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); struct hostinfo hi; + struct argv_array env = ARGV_ARRAY_INIT; hostinfo_init(&hi); @@ -716,8 +766,9 @@ static int execute(void) pktlen--; } + /* parse additional args hidden behind a NUL byte */ if (len != pktlen) - parse_host_arg(&hi, line + len + 1, pktlen - len - 1); + parse_extra_args(&hi, &env, line + len + 1, pktlen - len - 1); for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); @@ -730,13 +781,15 @@ static int execute(void) * Note: The directory here is probably context sensitive, * and might depend on the actual service being performed. */ - int rc = run_service(arg, s, &hi); + int rc = run_service(arg, s, &hi, &env); hostinfo_clear(&hi); + argv_array_clear(&env); return rc; } } hostinfo_clear(&hi); + argv_array_clear(&env); logerror("Protocol error: '%s'", line); return -1; } diff --git a/decorate.c b/decorate.c index 270eb25197..de31331fa4 100644 --- a/decorate.c +++ b/decorate.c @@ -14,20 +14,20 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n) static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration) { int size = n->size; - struct object_decoration *hash = n->hash; + struct decoration_entry *entries = n->entries; unsigned int j = hash_obj(base, size); - while (hash[j].base) { - if (hash[j].base == base) { - void *old = hash[j].decoration; - hash[j].decoration = decoration; + while (entries[j].base) { + if (entries[j].base == base) { + void *old = entries[j].decoration; + entries[j].decoration = decoration; return old; } if (++j >= size) j = 0; } - hash[j].base = base; - hash[j].decoration = decoration; + entries[j].base = base; + entries[j].decoration = decoration; n->nr++; return NULL; } @@ -36,24 +36,23 @@ static void grow_decoration(struct decoration *n) { int i; int old_size = n->size; - struct object_decoration *old_hash = n->hash; + struct decoration_entry *old_entries = n->entries; n->size = (old_size + 1000) * 3 / 2; - n->hash = xcalloc(n->size, sizeof(struct object_decoration)); + n->entries = xcalloc(n->size, sizeof(struct decoration_entry)); n->nr = 0; for (i = 0; i < old_size; i++) { - const struct object *base = old_hash[i].base; - void *decoration = old_hash[i].decoration; + const struct object *base = old_entries[i].base; + void *decoration = old_entries[i].decoration; if (!decoration) continue; insert_decoration(n, base, decoration); } - free(old_hash); + free(old_entries); } -/* Add a decoration pointer, return any old one */ void *add_decoration(struct decoration *n, const struct object *obj, void *decoration) { @@ -64,7 +63,6 @@ void *add_decoration(struct decoration *n, const struct object *obj, return insert_decoration(n, obj, decoration); } -/* Lookup a decoration pointer */ void *lookup_decoration(struct decoration *n, const struct object *obj) { unsigned int j; @@ -74,7 +72,7 @@ void *lookup_decoration(struct decoration *n, const struct object *obj) return NULL; j = hash_obj(obj, n->size); for (;;) { - struct object_decoration *ref = n->hash + j; + struct decoration_entry *ref = n->entries + j; if (ref->base == obj) return ref->decoration; if (!ref->base) diff --git a/decorate.h b/decorate.h index e7328044ff..9014c1e996 100644 --- a/decorate.h +++ b/decorate.h @@ -1,18 +1,61 @@ #ifndef DECORATE_H #define DECORATE_H -struct object_decoration { +/* + * A data structure that associates Git objects to void pointers. See + * t/helper/test-example-decorate.c for a demonstration of how to use these + * functions. + */ + +/* + * An entry in the data structure. + */ +struct decoration_entry { const struct object *base; void *decoration; }; +/* + * The data structure. + * + * This data structure must be zero-initialized. + */ struct decoration { + /* + * Not used by the decoration mechanism. Clients may use this for + * whatever they want. + */ const char *name; - unsigned int size, nr; - struct object_decoration *hash; + + /* + * The capacity of "entries". + */ + unsigned int size; + + /* + * The number of real Git objects (that is, entries with non-NULL + * "base"). + */ + unsigned int nr; + + /* + * The entries. This is an array of size "size", containing nr entries + * with non-NULL "base" and (size - nr) entries with NULL "base". + */ + struct decoration_entry *entries; }; +/* + * Add an association from the given object to the given pointer (which may be + * NULL), returning the previously associated pointer. If there is no previous + * association, this function returns NULL. + */ extern void *add_decoration(struct decoration *n, const struct object *obj, void *decoration); + +/* + * Return the pointer associated to the given object. If there is no + * association, this function returns NULL. + */ extern void *lookup_decoration(struct decoration *n, const struct object *obj); #endif diff --git a/diff-lib.c b/diff-lib.c index 5173023cd3..8104603a3b 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -218,7 +218,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } else if (revs->diffopt.ita_invisible_in_index && ce_intent_to_add(ce)) { diff_addremove(&revs->diffopt, '+', ce->ce_mode, - &empty_tree_oid, 0, + the_hash_algo->empty_tree, 0, ce->name, 0); continue; } @@ -3210,6 +3210,8 @@ static void builtin_diff(const char *name_a, ecbdata.opt = o; ecbdata.header = header.len ? &header : NULL; xpp.flags = o->xdl_opts; + xpp.anchors = o->anchors; + xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -3302,6 +3304,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = o->xdl_opts; + xpp.anchors = o->anchors; + xpp.anchors_nr = o->anchors_nr; xecfg.ctxlen = o->context; xecfg.interhunkctxlen = o->interhunkcontext; if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, @@ -4129,9 +4133,7 @@ void diff_setup_done(struct diff_options *options) * inside contents. */ - if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) || - DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) || - DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL)) + if ((options->xdl_opts & XDF_WHITESPACE_FLAGS)) options->flags.diff_from_contents = 1; else options->flags.diff_from_contents = 0; @@ -4510,17 +4512,12 @@ int diff_opt_parse(struct diff_options *options, options->output_format |= DIFF_FORMAT_NUMSTAT; else if (!strcmp(arg, "--shortstat")) options->output_format |= DIFF_FORMAT_SHORTSTAT; - else if (!strcmp(arg, "-X") || !strcmp(arg, "--dirstat")) - return parse_dirstat_opt(options, ""); - else if (skip_prefix(arg, "-X", &arg)) - return parse_dirstat_opt(options, arg); - else if (skip_prefix(arg, "--dirstat=", &arg)) + else if (skip_prefix(arg, "-X", &arg) || + skip_to_optional_arg(arg, "--dirstat", &arg)) return parse_dirstat_opt(options, arg); else if (!strcmp(arg, "--cumulative")) return parse_dirstat_opt(options, "cumulative"); - else if (!strcmp(arg, "--dirstat-by-file")) - return parse_dirstat_opt(options, "files"); - else if (skip_prefix(arg, "--dirstat-by-file=", &arg)) { + else if (skip_to_optional_arg(arg, "--dirstat-by-file", &arg)) { parse_dirstat_opt(options, "files"); return parse_dirstat_opt(options, arg); } @@ -4542,13 +4539,13 @@ int diff_opt_parse(struct diff_options *options, return stat_opt(options, av); /* renames options */ - else if (starts_with(arg, "-B") || starts_with(arg, "--break-rewrites=") || - !strcmp(arg, "--break-rewrites")) { + else if (starts_with(arg, "-B") || + skip_to_optional_arg(arg, "--break-rewrites", NULL)) { if ((options->break_opt = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -B: %s", arg+2); } - else if (starts_with(arg, "-M") || starts_with(arg, "--find-renames=") || - !strcmp(arg, "--find-renames")) { + else if (starts_with(arg, "-M") || + skip_to_optional_arg(arg, "--find-renames", NULL)) { if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) return error("invalid argument to -M: %s", arg+2); options->detect_rename = DIFF_DETECT_RENAME; @@ -4556,8 +4553,8 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "-D") || !strcmp(arg, "--irreversible-delete")) { options->irreversible_delete = 1; } - else if (starts_with(arg, "-C") || starts_with(arg, "--find-copies=") || - !strcmp(arg, "--find-copies")) { + else if (starts_with(arg, "-C") || + skip_to_optional_arg(arg, "--find-copies", NULL)) { if (options->detect_rename == DIFF_DETECT_COPY) options->flags.find_copies_harder = 1; if ((options->rename_score = diff_scoreopt_parse(arg)) == -1) @@ -4570,11 +4567,10 @@ int diff_opt_parse(struct diff_options *options, options->flags.rename_empty = 1; else if (!strcmp(arg, "--no-rename-empty")) options->flags.rename_empty = 0; - else if (!strcmp(arg, "--relative")) - options->flags.relative_name = 1; - else if (skip_prefix(arg, "--relative=", &arg)) { + else if (skip_to_optional_arg_default(arg, "--relative", &arg, NULL)) { options->flags.relative_name = 1; - options->prefix = arg; + if (arg) + options->prefix = arg; } /* xdiff options */ @@ -4588,15 +4584,26 @@ int diff_opt_parse(struct diff_options *options, DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); else if (!strcmp(arg, "--ignore-space-at-eol")) DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); + else if (!strcmp(arg, "--ignore-cr-at-eol")) + DIFF_XDL_SET(options, IGNORE_CR_AT_EOL); else if (!strcmp(arg, "--ignore-blank-lines")) DIFF_XDL_SET(options, IGNORE_BLANK_LINES); else if (!strcmp(arg, "--indent-heuristic")) DIFF_XDL_SET(options, INDENT_HEURISTIC); else if (!strcmp(arg, "--no-indent-heuristic")) DIFF_XDL_CLR(options, INDENT_HEURISTIC); - else if (!strcmp(arg, "--patience")) + else if (!strcmp(arg, "--patience")) { + int i; options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); - else if (!strcmp(arg, "--histogram")) + /* + * Both --patience and --anchored use PATIENCE_DIFF + * internally, so remove any anchors previously + * specified. + */ + for (i = 0; i < options->anchors_nr; i++) + free(options->anchors[i]); + options->anchors_nr = 0; + } else if (!strcmp(arg, "--histogram")) options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF); else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) { long value = parse_algorithm_value(optarg); @@ -4608,6 +4615,11 @@ int diff_opt_parse(struct diff_options *options, options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; options->xdl_opts |= value; return argcount; + } else if (skip_prefix(arg, "--anchored=", &arg)) { + options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); + ALLOC_GROW(options->anchors, options->anchors_nr + 1, + options->anchors_alloc); + options->anchors[options->anchors_nr++] = xstrdup(arg); } /* flags options */ @@ -4628,9 +4640,7 @@ int diff_opt_parse(struct diff_options *options, else if (!strcmp(arg, "--no-follow")) { options->flags.follow_renames = 0; options->flags.default_follow_renames = 0; - } else if (!strcmp(arg, "--color")) - options->use_color = 1; - else if (skip_prefix(arg, "--color=", &arg)) { + } else if (skip_to_optional_arg_default(arg, "--color", &arg, "always")) { int value = git_config_colorbool(NULL, arg); if (value < 0) return error("option `color' expects \"always\", \"auto\", or \"never\""); @@ -4650,15 +4660,10 @@ int diff_opt_parse(struct diff_options *options, if (cm < 0) die("bad --color-moved argument: %s", arg); options->color_moved = cm; - } else if (!strcmp(arg, "--color-words")) { + } else if (skip_to_optional_arg_default(arg, "--color-words", &options->word_regex, NULL)) { options->use_color = 1; options->word_diff = DIFF_WORDS_COLOR; } - else if (skip_prefix(arg, "--color-words=", &arg)) { - options->use_color = 1; - options->word_diff = DIFF_WORDS_COLOR; - options->word_regex = arg; - } else if (!strcmp(arg, "--word-diff")) { if (options->word_diff == DIFF_WORDS_NONE) options->word_diff = DIFF_WORDS_PLAIN; @@ -4696,15 +4701,10 @@ int diff_opt_parse(struct diff_options *options, options->flags.textconv_set_via_cmdline = 1; } else if (!strcmp(arg, "--no-textconv")) options->flags.allow_textconv = 0; - else if (!strcmp(arg, "--ignore-submodules")) { - options->flags.override_submodule_config = 1; - handle_ignore_submodules_arg(options, "all"); - } else if (skip_prefix(arg, "--ignore-submodules=", &arg)) { + else if (skip_to_optional_arg_default(arg, "--ignore-submodules", &arg, "all")) { options->flags.override_submodule_config = 1; handle_ignore_submodules_arg(options, arg); - } else if (!strcmp(arg, "--submodule")) - options->submodule_format = DIFF_SUBMODULE_LOG; - else if (skip_prefix(arg, "--submodule=", &arg)) + } else if (skip_to_optional_arg_default(arg, "--submodule", &arg, "log")) return parse_submodule_opt(options, arg); else if (skip_prefix(arg, "--ws-error-highlight=", &arg)) return parse_ws_error_highlight_opt(options, arg); @@ -4902,14 +4902,20 @@ const char *diff_aligned_abbrev(const struct object_id *oid, int len) int abblen; const char *abbrev; + /* Do we want all 40 hex characters? */ if (len == GIT_SHA1_HEXSZ) return oid_to_hex(oid); + /* An abbreviated value is fine, possibly followed by an ellipsis. */ abbrev = diff_abbrev_oid(oid, len); + + if (!print_sha1_ellipsis()) + return abbrev; + abblen = strlen(abbrev); /* - * In well-behaved cases, where the abbbreviated result is the + * In well-behaved cases, where the abbreviated result is the * same as the requested length, append three dots after the * abbreviation (hence the whole logic is limited to the case * where abblen < 37); when the actual abbreviated result is a @@ -5454,7 +5460,7 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) warning(_(rename_limit_warning)); else return; - if (0 < needed && needed < 32767) + if (0 < needed) warning(_(rename_limit_advice), varname, needed); } @@ -166,6 +166,10 @@ struct diff_options { const char *stat_sep; long xdl_opts; + /* see Documentation/diff-options.txt */ + char **anchors; + size_t anchors_nr, anchors_alloc; + int stat_width; int stat_name_width; int stat_graph_width; diff --git a/diffcore-rename.c b/diffcore-rename.c index 12dc2a056f..245e999fe5 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -391,14 +391,12 @@ static int too_many_rename_candidates(int num_create, * growing larger than a "rename_limit" square matrix, ie: * * num_create * num_src > rename_limit * rename_limit - * - * but handles the potential overflow case specially (and we - * assume at least 32-bit integers) */ - if (rename_limit <= 0 || rename_limit > 32767) + if (rename_limit <= 0) rename_limit = 32767; if ((num_create <= rename_limit || num_src <= rename_limit) && - (num_create * num_src <= rename_limit * rename_limit)) + ((uint64_t)num_create * (uint64_t)num_src + <= (uint64_t)rename_limit * (uint64_t)rename_limit)) return 0; options->needed_rename_limit = @@ -415,7 +413,8 @@ static int too_many_rename_candidates(int num_create, num_src++; } if ((num_create <= rename_limit || num_src <= rename_limit) && - (num_create * num_src <= rename_limit * rename_limit)) + ((uint64_t)num_create * (uint64_t)num_src + <= (uint64_t)rename_limit * (uint64_t)rename_limit)) return 2; return 1; } @@ -534,7 +533,7 @@ void diffcore_rename(struct diff_options *options) if (options->show_rename_progress) { progress = start_delayed_progress( _("Performing inexact rename detection"), - rename_dst_nr * rename_src_nr); + (uint64_t)rename_dst_nr * (uint64_t)rename_src_nr); } mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_create), sizeof(*mx)); @@ -571,7 +570,7 @@ void diffcore_rename(struct diff_options *options) diff_free_filespec_blob(two); } dst_cnt++; - display_progress(progress, (i+1)*rename_src_nr); + display_progress(progress, (uint64_t)(i+1)*(uint64_t)rename_src_nr); } stop_progress(&progress); @@ -221,6 +221,57 @@ int within_depth(const char *name, int namelen, return 1; } +/* + * Read the contents of the blob with the given OID into a buffer. + * Append a trailing LF to the end if the last line doesn't have one. + * + * Returns: + * -1 when the OID is invalid or unknown or does not refer to a blob. + * 0 when the blob is empty. + * 1 along with { data, size } of the (possibly augmented) buffer + * when successful. + * + * Optionally updates the given sha1_stat with the given OID (when valid). + */ +static int do_read_blob(const struct object_id *oid, + struct sha1_stat *sha1_stat, + size_t *size_out, + char **data_out) +{ + enum object_type type; + unsigned long sz; + char *data; + + *size_out = 0; + *data_out = NULL; + + data = read_sha1_file(oid->hash, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return -1; + } + + if (sha1_stat) { + memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat)); + hashcpy(sha1_stat->sha1, oid->hash); + } + + if (sz == 0) { + free(data); + return 0; + } + + if (data[sz - 1] != '\n') { + data = xrealloc(data, st_add(sz, 1)); + data[sz++] = '\n'; + } + + *size_out = xsize_t(sz); + *data_out = data; + + return 1; +} + #define DO_MATCH_EXCLUDE (1<<0) #define DO_MATCH_DIRECTORY (1<<1) #define DO_MATCH_SUBMODULE (1<<2) @@ -601,32 +652,22 @@ void add_exclude(const char *string, const char *base, x->el = el; } -static void *read_skip_worktree_file_from_index(const struct index_state *istate, - const char *path, size_t *size, - struct sha1_stat *sha1_stat) +static int read_skip_worktree_file_from_index(const struct index_state *istate, + const char *path, + size_t *size_out, + char **data_out, + struct sha1_stat *sha1_stat) { int pos, len; - unsigned long sz; - enum object_type type; - void *data; len = strlen(path); pos = index_name_pos(istate, path, len); if (pos < 0) - return NULL; + return -1; if (!ce_skip_worktree(istate->cache[pos])) - return NULL; - data = read_sha1_file(istate->cache[pos]->oid.hash, &type, &sz); - if (!data || type != OBJ_BLOB) { - free(data); - return NULL; - } - *size = xsize_t(sz); - if (sha1_stat) { - memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat)); - hashcpy(sha1_stat->sha1, istate->cache[pos]->oid.hash); - } - return data; + return -1; + + return do_read_blob(&istate->cache[pos]->oid, sha1_stat, size_out, data_out); } /* @@ -740,6 +781,10 @@ static void invalidate_directory(struct untracked_cache *uc, dir->dirs[i]->recurse = 0; } +static int add_excludes_from_buffer(char *buf, size_t size, + const char *base, int baselen, + struct exclude_list *el); + /* * Given a file with name "fname", read it (either from disk, or from * an index if 'istate' is non-null), parse it and store the @@ -755,9 +800,10 @@ static int add_excludes(const char *fname, const char *base, int baselen, struct sha1_stat *sha1_stat) { struct stat st; - int fd, i, lineno = 1; + int r; + int fd; size_t size = 0; - char *buf, *entry; + char *buf; fd = open(fname, O_RDONLY); if (fd < 0 || fstat(fd, &st) < 0) { @@ -765,17 +811,13 @@ static int add_excludes(const char *fname, const char *base, int baselen, warn_on_fopen_errors(fname); else close(fd); - if (!istate || - (buf = read_skip_worktree_file_from_index(istate, fname, &size, sha1_stat)) == NULL) + if (!istate) return -1; - if (size == 0) { - free(buf); - return 0; - } - if (buf[size-1] != '\n') { - buf = xrealloc(buf, st_add(size, 1)); - buf[size++] = '\n'; - } + r = read_skip_worktree_file_from_index(istate, fname, + &size, &buf, + sha1_stat); + if (r != 1) + return r; } else { size = xsize_t(st.st_size); if (size == 0) { @@ -814,6 +856,17 @@ static int add_excludes(const char *fname, const char *base, int baselen, } } + add_excludes_from_buffer(buf, size, base, baselen, el); + return 0; +} + +static int add_excludes_from_buffer(char *buf, size_t size, + const char *base, int baselen, + struct exclude_list *el) +{ + int i, lineno = 1; + char *entry; + el->filebuf = buf; if (skip_utf8_bom(&buf, size)) @@ -842,6 +895,23 @@ int add_excludes_from_file_to_list(const char *fname, const char *base, return add_excludes(fname, base, baselen, el, istate, NULL); } +int add_excludes_from_blob_to_list( + struct object_id *oid, + const char *base, int baselen, + struct exclude_list *el) +{ + char *buf; + size_t size; + int r; + + r = do_read_blob(oid, NULL, &size, &buf); + if (r != 1) + return r; + + add_excludes_from_buffer(buf, size, base, baselen, el); + return 0; +} + struct exclude_list *add_exclude_list(struct dir_struct *dir, int group_type, const char *src) { @@ -259,6 +259,9 @@ extern struct exclude_list *add_exclude_list(struct dir_struct *dir, extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, struct exclude_list *el, struct index_state *istate); extern void add_excludes_from_file(struct dir_struct *, const char *fname); +extern int add_excludes_from_blob_to_list(struct object_id *oid, + const char *base, int baselen, + struct exclude_list *el); extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *el, int srcpos); @@ -7,11 +7,16 @@ #define DEFAULT_EDITOR "vi" #endif +int is_terminal_dumb(void) +{ + const char *terminal = getenv("TERM"); + return !terminal || !strcmp(terminal, "dumb"); +} + const char *git_editor(void) { const char *editor = getenv("GIT_EDITOR"); - const char *terminal = getenv("TERM"); - int terminal_is_dumb = !terminal || !strcmp(terminal, "dumb"); + int terminal_is_dumb = is_terminal_dumb(); if (!editor && editor_program) editor = editor_program; @@ -40,6 +45,23 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en const char *args[] = { editor, real_path(path), NULL }; struct child_process p = CHILD_PROCESS_INIT; int ret, sig; + int print_waiting_for_editor = advice_waiting_for_editor && isatty(2); + + if (print_waiting_for_editor) { + /* + * A dumb terminal cannot erase the line later on. Add a + * newline to separate the hint from subsequent output. + * + * Make sure that our message is separated with a whitespace + * from further cruft that may be written by the editor. + */ + const char term = is_terminal_dumb() ? '\n' : ' '; + + fprintf(stderr, + _("hint: Waiting for your editor to close the file...%c"), + term); + fflush(stderr); + } p.argv = args; p.env = env; @@ -58,6 +80,13 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en if (ret) return error("There was a problem with the editor '%s'.", editor); + + if (print_waiting_for_editor && !is_terminal_dumb()) + /* + * Go back to the beginning and erase the entire line to + * avoid wasting the vertical space. + */ + fputs("\r\033[K", stderr); } if (!buffer) diff --git a/environment.c b/environment.c index 8fa032f307..63ac38a46f 100644 --- a/environment.c +++ b/environment.c @@ -344,3 +344,18 @@ int use_optional_locks(void) { return git_env_bool(GIT_OPTIONAL_LOCKS_ENVIRONMENT, 1); } + +int print_sha1_ellipsis(void) +{ + /* + * Determine if the calling environment contains the variable + * GIT_PRINT_SHA1_ELLIPSIS set to "yes". + */ + static int cached_result = -1; /* unknown */ + + if (cached_result < 0) { + const char *v = getenv("GIT_PRINT_SHA1_ELLIPSIS"); + cached_result = (v && !strcasecmp(v, "yes")); + } + return cached_result; +} diff --git a/fetch-pack.c b/fetch-pack.c index 008b25d3db..9f6b07ad91 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -716,7 +716,8 @@ static int everything_local(struct fetch_pack_args *args, for (ref = *refs; ref; ref = ref->next) { struct object *o; - if (!has_object_file(&ref->old_oid)) + if (!has_object_file_with_flags(&ref->old_oid, + OBJECT_INFO_QUICK)) continue; o = parse_object(&ref->old_oid); diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index ab0d1b0c06..eeea4b67ea 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -1,6 +1,6 @@ #!/bin/sh -echo "/* Automatically generated by $0 */ +echo "/* Automatically generated by generate-cmdlist.sh */ struct cmdname_help { char name[16]; char help[80]; diff --git a/git-compat-util.h b/git-compat-util.h index cedad4d581..68b2ad531e 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -485,6 +485,29 @@ static inline int skip_prefix(const char *str, const char *prefix, } /* + * If the string "str" is the same as the string in "prefix", then the "arg" + * parameter is set to the "def" parameter and 1 is returned. + * If the string "str" begins with the string found in "prefix" and then a + * "=" sign, then the "arg" parameter is set to "str + strlen(prefix) + 1" + * (i.e., to the point in the string right after the prefix and the "=" sign), + * and 1 is returned. + * + * Otherwise, return 0 and leave "arg" untouched. + * + * When we accept both a "--key" and a "--key=<val>" option, this function + * can be used instead of !strcmp(arg, "--key") and then + * skip_prefix(arg, "--key=", &arg) to parse such an option. + */ +int skip_to_optional_arg_default(const char *str, const char *prefix, + const char **arg, const char *def); + +static inline int skip_to_optional_arg(const char *str, const char *prefix, + const char **arg) +{ + return skip_to_optional_arg_default(str, prefix, arg, ""); +} + +/* * Like skip_prefix, but promises never to read past "len" bytes of the input * buffer, and returns the remaining number of bytes in "out" via "outlen". */ diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 36929921ea..2d8df83172 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -642,7 +642,7 @@ sub is_sha1 { sub get_headref ($) { my $name = shift; - $name =~ s/'/'\\''/; + $name =~ s/'/'\\''/g; my $r = `git rev-parse --verify '$name' 2>/dev/null`; return undef unless $? == 0; chomp $r; diff --git a/git-gui/Makefile b/git-gui/Makefile index 918a8de369..f10caedaa7 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -254,7 +254,7 @@ $(ALL_MSGFILES): %.msg : %.po lib/tclIndex: $(ALL_LIBFILES) GIT-GUI-VARS $(QUIET_INDEX)if echo \ $(foreach p,$(PRELOAD_FILES),source $p\;) \ - auto_mkindex lib '*.tcl' \ + auto_mkindex lib $(patsubst lib/%,%,$(sort $(ALL_LIBFILES))) \ | $(TCL_PATH) $(QUIET_2DEVNULL); then : ok; \ else \ echo >&2 " * $(TCL_PATH) failed; using unoptimized loading"; \ diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index ed24aa9d2f..91c00e6489 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -2501,9 +2501,28 @@ proc toggle_or_diff {mode w args} { set pos [split [$w index @$x,$y] .] foreach {lno col} $pos break } else { + if {$mode eq "toggle"} { + if {$w eq $ui_workdir} { + do_add_selection + set last_clicked {} + return + } + if {$w eq $ui_index} { + do_unstage_selection + set last_clicked {} + return + } + } + if {$last_clicked ne {}} { set lno [lindex $last_clicked 1] } else { + if {![info exists file_lists] + || ![info exists file_lists($w)] + || [llength $file_lists($w)] == 0} { + set last_clicked {} + return + } set lno [expr {int([lindex [$w tag ranges in_diff] 0])}] } if {$mode eq "toggle"} { @@ -2514,7 +2533,13 @@ proc toggle_or_diff {mode w args} { } } - set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {![info exists file_lists] + || ![info exists file_lists($w)] + || [llength $file_lists($w)] < $lno - 1} { + set path {} + } else { + set path [lindex $file_lists($w) [expr {$lno - 1}]] + } if {$path eq {}} { set last_clicked {} return diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 83620b7cbc..75ea965dac 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -25,6 +25,8 @@ You are currently in the middle of a merge that has not been fully completed. Y set msg {} set parents [list] if {[catch { + set name "" + set email "" set fd [git_read cat-file commit $curHEAD] fconfigure $fd -encoding binary -translation lf # By default commits are assumed to be in utf-8 @@ -34,9 +36,7 @@ You are currently in the middle of a merge that has not been fully completed. Y lappend parents [string range $line 7 end] } elseif {[string match {encoding *} $line]} { set enc [string tolower [string range $line 9 end]] - } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { - set commit_author [list name $name email $email date $time] - } + } elseif {[regexp "author (.*)\\s<(.*)>\\s(\\d.*$)" $line all name email time]} { } } set msg [read $fd] close $fd @@ -44,7 +44,13 @@ You are currently in the middle of a merge that has not been fully completed. Y set enc [tcl_encoding $enc] if {$enc ne {}} { set msg [encoding convertfrom $enc $msg] + set name [encoding convertfrom $enc $name] + set email [encoding convertfrom $enc $email] } + if {$name ne {} && $email ne {}} { + set commit_author [list name $name email $email date $time] + } + set msg [string trim $msg] } err]} { error_popup [strcat [mc "Error loading commit data for amend:"] "\n\n$err"] diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 4cae10a4c7..68c4a6c736 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -698,6 +698,7 @@ proc apply_range_or_line {x y} { set hh [$ui_diff get $i_l "$i_l + 1 lines"] set hh [lindex [split $hh ,] 0] set hln [lindex [split $hh -] 1] + set hln [lindex [split $hln " "] 0] # There is a special situation to take care of. Consider this # hunk: @@ -1178,6 +1178,12 @@ class Command: self.needsGit = True self.verbose = False + # This is required for the "append" cloneExclude action + def ensure_value(self, attr, value): + if not hasattr(self, attr) or getattr(self, attr) is None: + setattr(self, attr, value) + return getattr(self, attr) + class P4UserMap: def __init__(self): self.userMapFromPerforceServer = False @@ -1343,9 +1349,10 @@ class P4Submit(Command, P4UserMap): optparse.make_option("--shelve", dest="shelve", action="store_true", help="Shelve instead of submit. Shelved files are reverted, " "restoring the workspace to the state before the shelve"), - optparse.make_option("--update-shelve", dest="update_shelve", action="store", type="int", + optparse.make_option("--update-shelve", dest="update_shelve", action="append", type="int", metavar="CHANGELIST", - help="update an existing shelved changelist, implies --shelve") + help="update an existing shelved changelist, implies --shelve, " + "repeat in-order for multiple shelved changelists") ] self.description = "Submit changes from git to the perforce depot." self.usage += " [name of git branch to submit into perforce depot]" @@ -1354,7 +1361,7 @@ class P4Submit(Command, P4UserMap): self.preserveUser = gitConfigBool("git-p4.preserveUser") self.dry_run = False self.shelve = False - self.update_shelve = None + self.update_shelve = list() self.prepare_p4_only = False self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") @@ -1809,9 +1816,10 @@ class P4Submit(Command, P4UserMap): mode = filesToChangeExecBit[f] setP4ExecBit(f, mode) - if self.update_shelve: - print("all_files = %s" % str(all_files)) - p4_reopen_in_change(self.update_shelve, all_files) + update_shelve = 0 + if len(self.update_shelve) > 0: + update_shelve = self.update_shelve.pop(0) + p4_reopen_in_change(update_shelve, all_files) # # Build p4 change description, starting with the contents @@ -1821,7 +1829,7 @@ class P4Submit(Command, P4UserMap): logMessage = logMessage.strip() (logMessage, jobs) = self.separate_jobs_from_description(logMessage) - template = self.prepareSubmitTemplate(self.update_shelve) + template = self.prepareSubmitTemplate(update_shelve) submitTemplate = self.prepareLogMessage(template, logMessage, jobs) if self.preserveUser: @@ -1894,7 +1902,7 @@ class P4Submit(Command, P4UserMap): message = message.replace("\r\n", "\n") submitTemplate = message[:message.index(separatorLine)] - if self.update_shelve: + if update_shelve: p4_write_pipe(['shelve', '-r', '-i'], submitTemplate) elif self.shelve: p4_write_pipe(['shelve', '-i'], submitTemplate) @@ -2012,6 +2020,10 @@ class P4Submit(Command, P4UserMap): else: return False + for i in self.update_shelve: + if i <= 0: + sys.exit("invalid changelist %d" % i) + if self.master: allowSubmit = gitConfig("git-p4.allowSubmit") if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","): @@ -2022,7 +2034,7 @@ class P4Submit(Command, P4UserMap): if len(self.origin) == 0: self.origin = upstream - if self.update_shelve: + if len(self.update_shelve) > 0: self.shelve = True if self.preserveUser: @@ -2134,6 +2146,11 @@ class P4Submit(Command, P4UserMap): if gitConfigBool("git-p4.detectCopiesHarder"): self.diffOpts += " --find-copies-harder" + num_shelves = len(self.update_shelve) + if num_shelves > 0 and num_shelves != len(commits): + sys.exit("number of commits (%d) must match number of shelved changelist (%d)" % + (len(commits), num_shelves)) + # # Apply the commits, one at a time. On failure, ask if should # continue to try the rest of the patches, or quit. @@ -2404,12 +2421,6 @@ class P4Sync(Command, P4UserMap): if gitConfig("git-p4.syncFromOrigin") == "false": self.syncWithOrigin = False - # This is required for the "append" cloneExclude action - def ensure_value(self, attr, value): - if not hasattr(self, attr) or getattr(self, attr) is None: - setattr(self, attr, value) - return getattr(self, attr) - # Force a checkpoint in fast-import and wait for it to finish def checkpoint(self): self.gitStream.write("checkpoint\n\n") diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 6e64d40d6f..14c50782e0 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -53,6 +53,7 @@ else git format-patch -k --stdout --full-index --cherry-pick --right-only \ --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ + --pretty=mboxrd \ $git_format_patch_opt \ "$revisions" ${restrict_revision+^$restrict_revision} \ >"$GIT_DIR/rebased-patches" @@ -83,6 +84,7 @@ else fi git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" \ + --patch-format=mboxrd \ $allow_rerere_autoupdate \ ${gpg_sign_opt:+"$gpg_sign_opt"} <"$GIT_DIR/rebased-patches" ret=$? diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 437815669f..d47bd29593 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -392,9 +392,12 @@ pick_one_preserving_merges () { new_parents=${new_parents# $first_parent} merge_args="--no-log --no-ff" if ! do_with_author output eval \ - 'git merge ${gpg_sign_opt:+"$gpg_sign_opt"} \ - $allow_rerere_autoupdate $merge_args \ - $strategy_args -m "$msg_content" $new_parents' + git merge ${gpg_sign_opt:+$(git rev-parse \ + --sq-quote "$gpg_sign_opt")} \ + $allow_rerere_autoupdate "$merge_args" \ + "$strategy_args" \ + -m $(git rev-parse --sq-quote "$msg_content") \ + "$new_parents" then printf "%s\n" "$msg_content" > "$GIT_DIR"/MERGE_MSG die_with_patch $sha1 "$(eval_gettext "Error redoing merge \$sha1")" @@ -722,27 +725,6 @@ collapse_todo_ids() { git rebase--helper --shorten-ids } -# Add commands after a pick or after a squash/fixup series -# in the todo list. -add_exec_commands () { - { - first=t - while read -r insn rest - do - case $insn in - pick) - test -n "$first" || - printf "%s" "$cmd" - ;; - esac - printf "%s %s\n" "$insn" "$rest" - first= - done - printf "%s" "$cmd" - } <"$1" >"$1.new" && - mv "$1.new" "$1" -} - # Switch to the branch in $into and notify it in the reflog checkout_onto () { GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" @@ -914,7 +896,8 @@ fi if test t != "$preserve_merges" then git rebase--helper --make-script ${keep_empty:+--keep-empty} \ - $revisions ${restrict_revision+^$restrict_revision} >"$todo" + $revisions ${restrict_revision+^$restrict_revision} >"$todo" || + die "$(gettext "Could not generate todo list")" else format=$(git config --get rebase.instructionFormat) # the 'rev-list .. | sed' requires %m to parse; the instruction requires %H to parse @@ -982,7 +965,7 @@ fi test -s "$todo" || echo noop >> "$todo" test -z "$autosquash" || git rebase--helper --rearrange-squash || exit -test -n "$cmd" && add_exec_commands "$todo" +test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) todocount=${todocount##* } diff --git a/git-rebase.sh b/git-rebase.sh index 6344e8d5e3..fd72a35c65 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -9,7 +9,7 @@ OPTIONS_STUCKLONG=t OPTIONS_SPEC="\ git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] [<upstream>] [<branch>] git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] --root [<branch>] -git-rebase --continue | --abort | --skip | --edit-todo +git rebase --continue | --abort | --skip | --edit-todo -- Available options are v,verbose! display a diffstat of what changed upstream @@ -166,7 +166,7 @@ apply_autostash () { if test -f "$state_dir/autostash" then stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 2>&1 >/dev/null + if git stash apply $stash_sha1 >/dev/null 2>&1 then echo "$(gettext 'Applied autostash.')" >&2 else @@ -216,7 +216,7 @@ run_pre_rebase_hook () { } test -f "$apply_dir"/applying && - die "$(gettext "It looks like git-am is in progress. Cannot rebase.")" + die "$(gettext "It looks like 'git am' is in progress. Cannot rebase.")" if test -d "$apply_dir" then @@ -477,7 +477,7 @@ then ;; esac upstream=$(peel_committish "${upstream_name}") || - die "$(eval_gettext "invalid upstream \$upstream_name")" + die "$(eval_gettext "invalid upstream '\$upstream_name'")" upstream_arg="$upstream_name" else if test -z "$onto" @@ -518,7 +518,7 @@ case "$onto_name" in esac # If the branch to rebase is given, that is the branch we will rebase -# $branch_name -- branch being rebased, or HEAD (already detached) +# $branch_name -- branch/commit being rebased, or HEAD (already detached) # $orig_head -- commit object name of tip of the branch before rebasing # $head_name -- refs/heads/<that-branch> or "detached HEAD" switch_to= @@ -528,15 +528,18 @@ case "$#" in branch_name="$1" switch_to="$1" - if git show-ref --verify --quiet -- "refs/heads/$1" && - orig_head=$(git rev-parse -q --verify "refs/heads/$1") + # Is it a local branch? + if git show-ref --verify --quiet -- "refs/heads/$branch_name" && + orig_head=$(git rev-parse -q --verify "refs/heads/$branch_name") then - head_name="refs/heads/$1" - elif orig_head=$(git rev-parse -q --verify "$1") + head_name="refs/heads/$branch_name" + # If not is it a valid ref (branch or commit)? + elif orig_head=$(git rev-parse -q --verify "$branch_name") then head_name="detached HEAD" + else - die "$(eval_gettext "fatal: no such branch: \$branch_name")" + die "$(eval_gettext "fatal: no such branch/commit '\$branch_name'")" fi ;; 0) @@ -547,7 +550,7 @@ case "$#" in branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)') else head_name="detached HEAD" - branch_name=HEAD ;# detached + branch_name=HEAD fi orig_head=$(git rev-parse --verify HEAD) || exit ;; @@ -598,11 +601,23 @@ then test -z "$switch_to" || GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \ git checkout -q "$switch_to" -- - say "$(eval_gettext "Current branch \$branch_name is up to date.")" + if test "$branch_name" = "HEAD" && + ! git symbolic-ref -q HEAD + then + say "$(eval_gettext "HEAD is up to date.")" + else + say "$(eval_gettext "Current branch \$branch_name is up to date.")" + fi finish_rebase exit 0 else - say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" + if test "$branch_name" = "HEAD" && + ! git symbolic-ref -q HEAD + then + say "$(eval_gettext "HEAD is up to date, rebase forced.")" + else + say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" + fi fi fi diff --git a/git-send-email.perl b/git-send-email.perl index 2208dcc213..edcc6d3469 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -885,7 +885,9 @@ if (defined $initial_reply_to) { } if (!defined $smtp_server) { - foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { + my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail ); + push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH}; + foreach (@sendmail_paths) { if (-x $_) { $smtp_server = $_; last; diff --git a/git-stash.sh b/git-stash.sh index 4b74951440..1114005ce2 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -76,6 +76,12 @@ create_stash () { shift stash_msg=${1?"BUG: create_stash () -m requires an argument"} ;; + -m*) + stash_msg=${1#-m} + ;; + --message=*) + stash_msg=${1#--message=} + ;; -u|--include-untracked) shift untracked=${1?"BUG: create_stash () -u requires an argument"} @@ -193,6 +199,12 @@ store_stash () { shift stash_msg="$1" ;; + -m*) + stash_msg=${1#-m} + ;; + --message=*) + stash_msg=${1#--message=} + ;; -q|--quiet) quiet=t ;; @@ -251,6 +263,12 @@ push_stash () { test -z ${1+x} && usage stash_msg=$1 ;; + -m*) + stash_msg=${1#-m} + ;; + --message=*) + stash_msg=${1#--message=} + ;; --help) show_help ;; diff --git a/git-svn.perl b/git-svn.perl index d2404184ba..aa242d4f4f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1865,6 +1865,7 @@ sub get_commit_entry { } } $msgbuf =~ s/\s+$//s; + $msgbuf =~ s/\r\n/\n/sg; # SVN 1.6+ disallows CRLF if ($Git::SVN::_add_author_from && defined($author) && !$saw_from) { $msgbuf .= "\n\nFrom: $author"; @@ -372,7 +372,7 @@ static struct cmd_struct commands[] = { { "archive", cmd_archive, RUN_SETUP_GENTLY }, { "bisect--helper", cmd_bisect__helper, RUN_SETUP }, { "blame", cmd_blame, RUN_SETUP }, - { "branch", cmd_branch, RUN_SETUP }, + { "branch", cmd_branch, RUN_SETUP | DELAY_PAGER_CONFIG }, { "bundle", cmd_bundle, RUN_SETUP_GENTLY }, { "cat-file", cmd_cat_file, RUN_SETUP }, { "check-attr", cmd_check_attr, RUN_SETUP }, @@ -477,6 +477,8 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt int options = PCRE2_MULTILINE; const uint8_t *character_tables = NULL; int jitret; + int patinforet; + size_t jitsizearg; assert(opt->pcre2); @@ -511,6 +513,30 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE); if (jitret) die("Couldn't JIT the PCRE2 pattern '%s', got '%d'\n", p->pattern, jitret); + + /* + * The pcre2_config(PCRE2_CONFIG_JIT, ...) call just + * tells us whether the library itself supports JIT, + * but to see whether we're going to be actually using + * JIT we need to extract PCRE2_INFO_JITSIZE from the + * pattern *after* we do pcre2_jit_compile() above. + * + * This is because if the pattern contains the + * (*NO_JIT) verb (see pcre2syntax(3)) + * pcre2_jit_compile() will exit early with 0. If we + * then proceed to call pcre2_jit_match() further down + * the line instead of pcre2_match() we'll either + * segfault (pre PCRE 10.31) or run into a fatal error + * (post PCRE2 10.31) + */ + patinforet = pcre2_pattern_info(p->pcre2_pattern, PCRE2_INFO_JITSIZE, &jitsizearg); + if (patinforet) + BUG("pcre2_pattern_info() failed: %d", patinforet); + if (jitsizearg == 0) { + p->pcre2_jit_on = 0; + return; + } + p->pcre2_jit_stack = pcre2_jit_stack_create(1, 1024 * 1024, NULL); if (!p->pcre2_jit_stack) die("Couldn't allocate PCRE2 JIT stack"); @@ -1476,31 +1502,52 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs, } } +static int is_empty_line(const char *bol, const char *eol); + static void show_pre_context(struct grep_opt *opt, struct grep_source *gs, char *bol, char *end, unsigned lno) { - unsigned cur = lno, from = 1, funcname_lno = 0; - int funcname_needed = !!opt->funcname; - - if (opt->funcbody && !match_funcname(opt, gs, bol, end)) - funcname_needed = 2; + unsigned cur = lno, from = 1, funcname_lno = 0, orig_from; + int funcname_needed = !!opt->funcname, comment_needed = 0; if (opt->pre_context < lno) from = lno - opt->pre_context; if (from <= opt->last_shown) from = opt->last_shown + 1; + orig_from = from; + if (opt->funcbody) { + if (match_funcname(opt, gs, bol, end)) + comment_needed = 1; + else + funcname_needed = 1; + from = opt->last_shown + 1; + } /* Rewind. */ - while (bol > gs->buf && - cur > (funcname_needed == 2 ? opt->last_shown + 1 : from)) { + while (bol > gs->buf && cur > from) { + char *next_bol = bol; char *eol = --bol; while (bol > gs->buf && bol[-1] != '\n') bol--; cur--; + if (comment_needed && (is_empty_line(bol, eol) || + match_funcname(opt, gs, bol, eol))) { + comment_needed = 0; + from = orig_from; + if (cur < from) { + cur++; + bol = next_bol; + break; + } + } if (funcname_needed && match_funcname(opt, gs, bol, eol)) { funcname_lno = cur; funcname_needed = 0; + if (opt->funcbody) + comment_needed = 1; + else + from = orig_from; } } @@ -1,6 +1,8 @@ #ifndef HASH_H #define HASH_H +#include "git-compat-util.h" + #if defined(SHA1_PPC) #include "ppc/sha1.h" #elif defined(SHA1_APPLE) @@ -13,4 +15,59 @@ #include "block-sha1/sha1.h" #endif +/* + * Note that these constants are suitable for indexing the hash_algos array and + * comparing against each other, but are otherwise arbitrary, so they should not + * be exposed to the user or serialized to disk. To know whether a + * git_hash_algo struct points to some usable hash function, test the format_id + * field for being non-zero. Use the name field for user-visible situations and + * the format_id field for fixed-length fields on disk. + */ +/* An unknown hash function. */ +#define GIT_HASH_UNKNOWN 0 +/* SHA-1 */ +#define GIT_HASH_SHA1 1 +/* Number of algorithms supported (including unknown). */ +#define GIT_HASH_NALGOS (GIT_HASH_SHA1 + 1) + +typedef void (*git_hash_init_fn)(void *ctx); +typedef void (*git_hash_update_fn)(void *ctx, const void *in, size_t len); +typedef void (*git_hash_final_fn)(unsigned char *hash, void *ctx); + +struct git_hash_algo { + /* + * The name of the algorithm, as appears in the config file and in + * messages. + */ + const char *name; + + /* A four-byte version identifier, used in pack indices. */ + uint32_t format_id; + + /* The size of a hash context (e.g. git_SHA_CTX). */ + size_t ctxsz; + + /* The length of the hash in binary. */ + size_t rawsz; + + /* The length of the hash in hex characters. */ + size_t hexsz; + + /* The hash initialization function. */ + git_hash_init_fn init_fn; + + /* The hash update function. */ + git_hash_update_fn update_fn; + + /* The hash finalization function. */ + git_hash_final_fn final_fn; + + /* The OID of the empty tree. */ + const struct object_id *empty_tree; + + /* The OID of the empty blob. */ + const struct object_id *empty_blob; +}; +extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS]; + #endif @@ -18,75 +18,71 @@ * * #define COMPARE_VALUE 1 * - * static int long2string_cmp(const struct long2string *e1, + * static int long2string_cmp(const void *hashmap_cmp_fn_data, + * const struct long2string *e1, * const struct long2string *e2, - * const void *keydata, const void *userdata) + * const void *keydata) * { - * char *string = keydata; - * unsigned *flags = (unsigned*)userdata; + * const char *string = keydata; + * unsigned flags = *(unsigned *)hashmap_cmp_fn_data; * * if (flags & COMPARE_VALUE) - * return !(e1->key == e2->key) || (keydata ? - * strcmp(e1->value, keydata) : strcmp(e1->value, e2->value)); + * return e1->key != e2->key || + * strcmp(e1->value, string ? string : e2->value); * else - * return !(e1->key == e2->key); + * return e1->key != e2->key; * } * * int main(int argc, char **argv) * { * long key; - * char *value, *action; - * - * unsigned flags = ALLOW_DUPLICATE_KEYS; + * char value[255], action[32]; + * unsigned flags = 0; * * hashmap_init(&map, (hashmap_cmp_fn) long2string_cmp, &flags, 0); * - * while (scanf("%s %l %s", action, key, value)) { + * while (scanf("%s %ld %s", action, &key, value)) { * * if (!strcmp("add", action)) { * struct long2string *e; - * e = malloc(sizeof(struct long2string) + strlen(value)); + * FLEX_ALLOC_STR(e, value, value); * hashmap_entry_init(e, memhash(&key, sizeof(long))); * e->key = key; - * memcpy(e->value, value, strlen(value)); * hashmap_add(&map, e); * } * * if (!strcmp("print_all_by_key", action)) { - * flags &= ~COMPARE_VALUE; - * - * struct long2string k; + * struct long2string k, *e; * hashmap_entry_init(&k, memhash(&key, sizeof(long))); * k.key = key; * - * struct long2string *e = hashmap_get(&map, &k, NULL); + * flags &= ~COMPARE_VALUE; + * e = hashmap_get(&map, &k, NULL); * if (e) { - * printf("first: %l %s\n", e->key, e->value); - * while (e = hashmap_get_next(&map, e)) - * printf("found more: %l %s\n", e->key, e->value); + * printf("first: %ld %s\n", e->key, e->value); + * while ((e = hashmap_get_next(&map, e))) + * printf("found more: %ld %s\n", e->key, e->value); * } * } * * if (!strcmp("has_exact_match", action)) { - * flags |= COMPARE_VALUE; - * * struct long2string *e; - * e = malloc(sizeof(struct long2string) + strlen(value)); + * FLEX_ALLOC_STR(e, value, value); * hashmap_entry_init(e, memhash(&key, sizeof(long))); * e->key = key; - * memcpy(e->value, value, strlen(value)); * - * printf("%s found\n", hashmap_get(&map, e, NULL) ? "" : "not"); + * flags |= COMPARE_VALUE; + * printf("%sfound\n", hashmap_get(&map, e, NULL) ? "" : "not "); + * free(e); * } * * if (!strcmp("has_exact_match_no_heap_alloc", action)) { - * flags |= COMPARE_VALUE; - * - * struct long2string e; - * hashmap_entry_init(e, memhash(&key, sizeof(long))); - * e.key = key; + * struct long2string k; + * hashmap_entry_init(&k, memhash(&key, sizeof(long))); + * k.key = key; * - * printf("%s found\n", hashmap_get(&map, e, value) ? "" : "not"); + * flags |= COMPARE_VALUE; + * printf("%sfound\n", hashmap_get(&map, &k, value) ? "" : "not "); * } * * if (!strcmp("end", action)) { @@ -94,6 +90,8 @@ * break; * } * } + * + * return 0; * } */ @@ -412,6 +412,12 @@ int cmd_version(int argc, const char **argv, const char *prefix) printf("git version %s\n", git_version_string); if (build_options) { + printf("cpu: %s\n", GIT_HOST_CPU); + if (git_built_from_commit_string[0]) + printf("built from commit: %s\n", + git_built_from_commit_string); + else + printf("no commit associated with this build\n"); printf("sizeof-long: %d\n", (int)sizeof(long)); /* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ } @@ -12,6 +12,7 @@ #include "gettext.h" #include "transport.h" #include "packfile.h" +#include "protocol.h" static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); #if LIBCURL_VERSION_NUM >= 0x070a08 @@ -865,6 +866,11 @@ static CURL *get_curl_handle(void) curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); #endif +#if LIBCURL_VERSION_NUM >= 0x073400 + else if (starts_with(curl_http_proxy, "https")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_HTTPS); +#endif if (strstr(curl_http_proxy, "://")) credential_from_url(&proxy_auth, curl_http_proxy); else { @@ -898,6 +904,21 @@ static void set_from_env(const char **var, const char *envname) *var = val; } +static void protocol_http_header(void) +{ + if (get_protocol_version_config() > 0) { + struct strbuf protocol_header = STRBUF_INIT; + + strbuf_addf(&protocol_header, GIT_PROTOCOL_HEADER ": version=%d", + get_protocol_version_config()); + + + extra_http_headers = curl_slist_append(extra_http_headers, + protocol_header.buf); + strbuf_release(&protocol_header); + } +} + void http_init(struct remote *remote, const char *url, int proactive_auth) { char *low_speed_limit; @@ -928,6 +949,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) if (remote) var_override(&http_proxy_authmethod, remote->http_proxy_authmethod); + protocol_http_header(); + pragma_header = curl_slist_append(http_copy_default_headers(), "Pragma: no-cache"); no_pragma_header = curl_slist_append(http_copy_default_headers(), @@ -2007,7 +2030,6 @@ int finish_http_pack_request(struct http_pack_request *preq) char *tmp_idx; size_t len; struct child_process ip = CHILD_PROCESS_INIT; - const char *ip_argv[8]; close_pack_index(p); @@ -2023,13 +2045,9 @@ int finish_http_pack_request(struct http_pack_request *preq) die("BUG: pack tmpfile does not end in .pack.temp?"); tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile); - ip_argv[0] = "index-pack"; - ip_argv[1] = "-o"; - ip_argv[2] = tmp_idx; - ip_argv[3] = preq->tmpfile; - ip_argv[4] = NULL; - - ip.argv = ip_argv; + argv_array_push(&ip.args, "index-pack"); + argv_array_pushl(&ip.args, "-o", tmp_idx, NULL); + argv_array_push(&ip.args, preq->tmpfile); ip.git_cmd = 1; ip.no_stdin = 1; ip.no_stdout = 1; diff --git a/imap-send.c b/imap-send.c index 54e6a80fd6..36c7c1b4f6 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1412,6 +1412,7 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) { CURL *curl; struct strbuf path = STRBUF_INIT; + char *uri_encoded_folder; if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); @@ -1429,7 +1430,12 @@ static CURL *setup_curl(struct imap_server_conf *srvc, struct credential *cred) strbuf_addstr(&path, server.host); if (!path.len || path.buf[path.len - 1] != '/') strbuf_addch(&path, '/'); - strbuf_addstr(&path, server.folder); + + uri_encoded_folder = curl_easy_escape(curl, server.folder, 0); + if (!uri_encoded_folder) + die("failed to encode server folder"); + strbuf_addstr(&path, uri_encoded_folder); + curl_free(uri_encoded_folder); curl_easy_setopt(curl, CURLOPT_URL, path.buf); strbuf_release(&path); diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c new file mode 100644 index 0000000000..4c5b34e949 --- /dev/null +++ b/list-objects-filter-options.c @@ -0,0 +1,92 @@ +#include "cache.h" +#include "commit.h" +#include "config.h" +#include "revision.h" +#include "argv-array.h" +#include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" + +/* + * Parse value of the argument to the "filter" keyword. + * On the command line this looks like: + * --filter=<arg> + * and in the pack protocol as: + * "filter" SP <arg> + * + * The filter keyword will be used by many commands. + * See Documentation/rev-list-options.txt for allowed values for <arg>. + * + * Capture the given arg as the "filter_spec". This can be forwarded to + * subordinate commands when necessary. We also "intern" the arg for + * the convenience of the current command. + */ +int parse_list_objects_filter(struct list_objects_filter_options *filter_options, + const char *arg) +{ + const char *v0; + + if (filter_options->choice) + die(_("multiple object filter types cannot be combined")); + + filter_options->filter_spec = strdup(arg); + + if (!strcmp(arg, "blob:none")) { + filter_options->choice = LOFC_BLOB_NONE; + return 0; + } + + if (skip_prefix(arg, "blob:limit=", &v0)) { + if (!git_parse_ulong(v0, &filter_options->blob_limit_value)) + die(_("invalid filter-spec expression '%s'"), arg); + filter_options->choice = LOFC_BLOB_LIMIT; + return 0; + } + + if (skip_prefix(arg, "sparse:oid=", &v0)) { + struct object_context oc; + struct object_id sparse_oid; + + /* + * Try to parse <oid-expression> into an OID for the current + * command, but DO NOT complain if we don't have the blob or + * ref locally. + */ + if (!get_oid_with_context(v0, GET_OID_BLOB, + &sparse_oid, &oc)) + filter_options->sparse_oid_value = oiddup(&sparse_oid); + filter_options->choice = LOFC_SPARSE_OID; + return 0; + } + + if (skip_prefix(arg, "sparse:path=", &v0)) { + filter_options->choice = LOFC_SPARSE_PATH; + filter_options->sparse_path_value = strdup(v0); + return 0; + } + + die(_("invalid filter-spec expression '%s'"), arg); + return 0; +} + +int opt_parse_list_objects_filter(const struct option *opt, + const char *arg, int unset) +{ + struct list_objects_filter_options *filter_options = opt->value; + + if (unset || !arg) { + list_objects_filter_release(filter_options); + return 0; + } + + return parse_list_objects_filter(filter_options, arg); +} + +void list_objects_filter_release( + struct list_objects_filter_options *filter_options) +{ + free(filter_options->filter_spec); + free(filter_options->sparse_oid_value); + free(filter_options->sparse_path_value); + memset(filter_options, 0, sizeof(*filter_options)); +} diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h new file mode 100644 index 0000000000..eea44a1a51 --- /dev/null +++ b/list-objects-filter-options.h @@ -0,0 +1,61 @@ +#ifndef LIST_OBJECTS_FILTER_OPTIONS_H +#define LIST_OBJECTS_FILTER_OPTIONS_H + +#include "parse-options.h" + +/* + * The list of defined filters for list-objects. + */ +enum list_objects_filter_choice { + LOFC_DISABLED = 0, + LOFC_BLOB_NONE, + LOFC_BLOB_LIMIT, + LOFC_SPARSE_OID, + LOFC_SPARSE_PATH, + LOFC__COUNT /* must be last */ +}; + +struct list_objects_filter_options { + /* + * 'filter_spec' is the raw argument value given on the command line + * or protocol request. (The part after the "--keyword=".) For + * commands that launch filtering sub-processes, this value should be + * passed to them as received by the current process. + */ + char *filter_spec; + + /* + * 'choice' is determined by parsing the filter-spec. This indicates + * the filtering algorithm to use. + */ + enum list_objects_filter_choice choice; + + /* + * Parsed values (fields) from within the filter-spec. These are + * choice-specific; not all values will be defined for any given + * choice. + */ + struct object_id *sparse_oid_value; + char *sparse_path_value; + unsigned long blob_limit_value; +}; + +/* Normalized command line arguments */ +#define CL_ARG__FILTER "filter" + +int parse_list_objects_filter( + struct list_objects_filter_options *filter_options, + const char *arg); + +int opt_parse_list_objects_filter(const struct option *opt, + const char *arg, int unset); + +#define OPT_PARSE_LIST_OBJECTS_FILTER(fo) \ + { OPTION_CALLBACK, 0, CL_ARG__FILTER, fo, N_("args"), \ + N_("object filtering"), 0, \ + opt_parse_list_objects_filter } + +void list_objects_filter_release( + struct list_objects_filter_options *filter_options); + +#endif /* LIST_OBJECTS_FILTER_OPTIONS_H */ diff --git a/list-objects-filter.c b/list-objects-filter.c new file mode 100644 index 0000000000..4356c45368 --- /dev/null +++ b/list-objects-filter.c @@ -0,0 +1,401 @@ +#include "cache.h" +#include "dir.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" +#include "tree-walk.h" +#include "revision.h" +#include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" +#include "oidset.h" + +/* Remember to update object flag allocation in object.h */ +/* + * FILTER_SHOWN_BUT_REVISIT -- we set this bit on tree objects + * that have been shown, but should be revisited if they appear + * in the traversal (until we mark it SEEN). This is a way to + * let us silently de-dup calls to show() in the caller. This + * is subtly different from the "revision.h:SHOWN" and the + * "sha1_name.c:ONELINE_SEEN" bits. And also different from + * the non-de-dup usage in pack-bitmap.c + */ +#define FILTER_SHOWN_BUT_REVISIT (1<<21) + +/* + * A filter for list-objects to omit ALL blobs from the traversal. + * And to OPTIONALLY collect a list of the omitted OIDs. + */ +struct filter_blobs_none_data { + struct oidset *omits; +}; + +static enum list_objects_filter_result filter_blobs_none( + enum list_objects_filter_situation filter_situation, + struct object *obj, + const char *pathname, + const char *filename, + void *filter_data_) +{ + struct filter_blobs_none_data *filter_data = filter_data_; + + switch (filter_situation) { + default: + die("unknown filter_situation"); + return LOFR_ZERO; + + case LOFS_BEGIN_TREE: + assert(obj->type == OBJ_TREE); + /* always include all tree objects */ + return LOFR_MARK_SEEN | LOFR_DO_SHOW; + + case LOFS_END_TREE: + assert(obj->type == OBJ_TREE); + return LOFR_ZERO; + + case LOFS_BLOB: + assert(obj->type == OBJ_BLOB); + assert((obj->flags & SEEN) == 0); + + if (filter_data->omits) + oidset_insert(filter_data->omits, &obj->oid); + return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ + } +} + +static void *filter_blobs_none__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn) +{ + struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d)); + d->omits = omitted; + + *filter_fn = filter_blobs_none; + *filter_free_fn = free; + return d; +} + +/* + * A filter for list-objects to omit large blobs. + * And to OPTIONALLY collect a list of the omitted OIDs. + */ +struct filter_blobs_limit_data { + struct oidset *omits; + unsigned long max_bytes; +}; + +static enum list_objects_filter_result filter_blobs_limit( + enum list_objects_filter_situation filter_situation, + struct object *obj, + const char *pathname, + const char *filename, + void *filter_data_) +{ + struct filter_blobs_limit_data *filter_data = filter_data_; + unsigned long object_length; + enum object_type t; + + switch (filter_situation) { + default: + die("unknown filter_situation"); + return LOFR_ZERO; + + case LOFS_BEGIN_TREE: + assert(obj->type == OBJ_TREE); + /* always include all tree objects */ + return LOFR_MARK_SEEN | LOFR_DO_SHOW; + + case LOFS_END_TREE: + assert(obj->type == OBJ_TREE); + return LOFR_ZERO; + + case LOFS_BLOB: + assert(obj->type == OBJ_BLOB); + assert((obj->flags & SEEN) == 0); + + t = sha1_object_info(obj->oid.hash, &object_length); + if (t != OBJ_BLOB) { /* probably OBJ_NONE */ + /* + * We DO NOT have the blob locally, so we cannot + * apply the size filter criteria. Be conservative + * and force show it (and let the caller deal with + * the ambiguity). + */ + goto include_it; + } + + if (object_length < filter_data->max_bytes) + goto include_it; + + if (filter_data->omits) + oidset_insert(filter_data->omits, &obj->oid); + return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ + } + +include_it: + if (filter_data->omits) + oidset_remove(filter_data->omits, &obj->oid); + return LOFR_MARK_SEEN | LOFR_DO_SHOW; +} + +static void *filter_blobs_limit__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn) +{ + struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d)); + d->omits = omitted; + d->max_bytes = filter_options->blob_limit_value; + + *filter_fn = filter_blobs_limit; + *filter_free_fn = free; + return d; +} + +/* + * A filter driven by a sparse-checkout specification to only + * include blobs that a sparse checkout would populate. + * + * The sparse-checkout spec can be loaded from a blob with the + * given OID or from a local pathname. We allow an OID because + * the repo may be bare or we may be doing the filtering on the + * server. + */ +struct frame { + /* + * defval is the usual default include/exclude value that + * should be inherited as we recurse into directories based + * upon pattern matching of the directory itself or of a + * containing directory. + */ + int defval; + + /* + * 1 if the directory (recursively) contains any provisionally + * omitted objects. + * + * 0 if everything (recursively) contained in this directory + * has been explicitly included (SHOWN) in the result and + * the directory may be short-cut later in the traversal. + */ + unsigned child_prov_omit : 1; +}; + +struct filter_sparse_data { + struct oidset *omits; + struct exclude_list el; + + size_t nr, alloc; + struct frame *array_frame; +}; + +static enum list_objects_filter_result filter_sparse( + enum list_objects_filter_situation filter_situation, + struct object *obj, + const char *pathname, + const char *filename, + void *filter_data_) +{ + struct filter_sparse_data *filter_data = filter_data_; + int val, dtype; + struct frame *frame; + + switch (filter_situation) { + default: + die("unknown filter_situation"); + return LOFR_ZERO; + + case LOFS_BEGIN_TREE: + assert(obj->type == OBJ_TREE); + dtype = DT_DIR; + val = is_excluded_from_list(pathname, strlen(pathname), + filename, &dtype, &filter_data->el, + &the_index); + if (val < 0) + val = filter_data->array_frame[filter_data->nr].defval; + + ALLOC_GROW(filter_data->array_frame, filter_data->nr + 1, + filter_data->alloc); + filter_data->nr++; + filter_data->array_frame[filter_data->nr].defval = val; + filter_data->array_frame[filter_data->nr].child_prov_omit = 0; + + /* + * A directory with this tree OID may appear in multiple + * places in the tree. (Think of a directory move or copy, + * with no other changes, so the OID is the same, but the + * full pathnames of objects within this directory are new + * and may match is_excluded() patterns differently.) + * So we cannot mark this directory as SEEN (yet), since + * that will prevent process_tree() from revisiting this + * tree object with other pathname prefixes. + * + * Only _DO_SHOW the tree object the first time we visit + * this tree object. + * + * We always show all tree objects. A future optimization + * may want to attempt to narrow this. + */ + if (obj->flags & FILTER_SHOWN_BUT_REVISIT) + return LOFR_ZERO; + obj->flags |= FILTER_SHOWN_BUT_REVISIT; + return LOFR_DO_SHOW; + + case LOFS_END_TREE: + assert(obj->type == OBJ_TREE); + assert(filter_data->nr > 0); + + frame = &filter_data->array_frame[filter_data->nr]; + filter_data->nr--; + + /* + * Tell our parent directory if any of our children were + * provisionally omitted. + */ + filter_data->array_frame[filter_data->nr].child_prov_omit |= + frame->child_prov_omit; + + /* + * If there are NO provisionally omitted child objects (ALL child + * objects in this folder were INCLUDED), then we can mark the + * folder as SEEN (so we will not have to revisit it again). + */ + if (!frame->child_prov_omit) + return LOFR_MARK_SEEN; + return LOFR_ZERO; + + case LOFS_BLOB: + assert(obj->type == OBJ_BLOB); + assert((obj->flags & SEEN) == 0); + + frame = &filter_data->array_frame[filter_data->nr]; + + dtype = DT_REG; + val = is_excluded_from_list(pathname, strlen(pathname), + filename, &dtype, &filter_data->el, + &the_index); + if (val < 0) + val = frame->defval; + if (val > 0) { + if (filter_data->omits) + oidset_remove(filter_data->omits, &obj->oid); + return LOFR_MARK_SEEN | LOFR_DO_SHOW; + } + + /* + * Provisionally omit it. We've already established that + * this pathname is not in the sparse-checkout specification + * with the CURRENT pathname, so we *WANT* to omit this blob. + * + * However, a pathname elsewhere in the tree may also + * reference this same blob, so we cannot reject it yet. + * Leave the LOFR_ bits unset so that if the blob appears + * again in the traversal, we will be asked again. + */ + if (filter_data->omits) + oidset_insert(filter_data->omits, &obj->oid); + + /* + * Remember that at least 1 blob in this tree was + * provisionally omitted. This prevents us from short + * cutting the tree in future iterations. + */ + frame->child_prov_omit = 1; + return LOFR_ZERO; + } +} + + +static void filter_sparse_free(void *filter_data) +{ + struct filter_sparse_data *d = filter_data; + /* TODO free contents of 'd' */ + free(d); +} + +static void *filter_sparse_oid__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn) +{ + struct filter_sparse_data *d = xcalloc(1, sizeof(*d)); + d->omits = omitted; + if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value, + NULL, 0, &d->el) < 0) + die("could not load filter specification"); + + ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc); + d->array_frame[d->nr].defval = 0; /* default to include */ + d->array_frame[d->nr].child_prov_omit = 0; + + *filter_fn = filter_sparse; + *filter_free_fn = filter_sparse_free; + return d; +} + +static void *filter_sparse_path__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn) +{ + struct filter_sparse_data *d = xcalloc(1, sizeof(*d)); + d->omits = omitted; + if (add_excludes_from_file_to_list(filter_options->sparse_path_value, + NULL, 0, &d->el, NULL) < 0) + die("could not load filter specification"); + + ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc); + d->array_frame[d->nr].defval = 0; /* default to include */ + d->array_frame[d->nr].child_prov_omit = 0; + + *filter_fn = filter_sparse; + *filter_free_fn = filter_sparse_free; + return d; +} + +typedef void *(*filter_init_fn)( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn); + +/* + * Must match "enum list_objects_filter_choice". + */ +static filter_init_fn s_filters[] = { + NULL, + filter_blobs_none__init, + filter_blobs_limit__init, + filter_sparse_oid__init, + filter_sparse_path__init, +}; + +void *list_objects_filter__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn) +{ + filter_init_fn init_fn; + + assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT); + + if (filter_options->choice >= LOFC__COUNT) + die("invalid list-objects filter choice: %d", + filter_options->choice); + + init_fn = s_filters[filter_options->choice]; + if (init_fn) + return init_fn(omitted, filter_options, + filter_fn, filter_free_fn); + *filter_fn = NULL; + *filter_free_fn = NULL; + return NULL; +} diff --git a/list-objects-filter.h b/list-objects-filter.h new file mode 100644 index 0000000000..a963d0274c --- /dev/null +++ b/list-objects-filter.h @@ -0,0 +1,77 @@ +#ifndef LIST_OBJECTS_FILTER_H +#define LIST_OBJECTS_FILTER_H + +/* + * During list-object traversal we allow certain objects to be + * filtered (omitted) from the result. The active filter uses + * these result values to guide list-objects. + * + * _ZERO : Do nothing with the object at this time. It may + * be revisited if it appears in another place in + * the tree or in another commit during the overall + * traversal. + * + * _MARK_SEEN : Mark this object as "SEEN" in the object flags. + * This will prevent it from being revisited during + * the remainder of the traversal. This DOES NOT + * imply that it will be included in the results. + * + * _DO_SHOW : Show this object in the results (call show() on it). + * In general, objects should only be shown once, but + * this result DOES NOT imply that we mark it SEEN. + * + * Most of the time, you want the combination (_MARK_SEEN | _DO_SHOW) + * but they can be used independently, such as when sparse-checkout + * pattern matching is being applied. + * + * A _MARK_SEEN without _DO_SHOW can be called a hard-omit -- the + * object is not shown and will never be reconsidered (unless a + * previous iteration has already shown it). + * + * A _DO_SHOW without _MARK_SEEN can be used, for example, to + * include a directory, but then revisit it to selectively include + * or omit objects within it. + * + * A _ZERO can be called a provisional-omit -- the object is NOT shown, + * but *may* be revisited (if the object appears again in the traversal). + * Therefore, it will be omitted from the results *unless* a later + * iteration causes it to be shown. + */ +enum list_objects_filter_result { + LOFR_ZERO = 0, + LOFR_MARK_SEEN = 1<<0, + LOFR_DO_SHOW = 1<<1, +}; + +enum list_objects_filter_situation { + LOFS_BEGIN_TREE, + LOFS_END_TREE, + LOFS_BLOB +}; + +typedef enum list_objects_filter_result (*filter_object_fn)( + enum list_objects_filter_situation filter_situation, + struct object *obj, + const char *pathname, + const char *filename, + void *filter_data); + +typedef void (*filter_free_fn)(void *filter_data); + +/* + * Constructor for the set of defined list-objects filters. + * Returns a generic "void *filter_data". + * + * The returned "filter_fn" will be used by traverse_commit_list() + * to filter the results. + * + * The returned "filter_free_fn" is a destructor for the + * filter_data. + */ +void *list_objects_filter__init( + struct oidset *omitted, + struct list_objects_filter_options *filter_options, + filter_object_fn *filter_fn, + filter_free_fn *filter_free_fn); + +#endif /* LIST_OBJECTS_FILTER_H */ diff --git a/list-objects.c b/list-objects.c index b3931fa434..0966cdc9fa 100644 --- a/list-objects.c +++ b/list-objects.c @@ -7,16 +7,21 @@ #include "tree-walk.h" #include "revision.h" #include "list-objects.h" +#include "list-objects-filter.h" +#include "list-objects-filter-options.h" static void process_blob(struct rev_info *revs, struct blob *blob, show_object_fn show, struct strbuf *path, const char *name, - void *cb_data) + void *cb_data, + filter_object_fn filter_fn, + void *filter_data) { struct object *obj = &blob->object; size_t pathlen; + enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW; if (!revs->blob_objects) return; @@ -24,11 +29,17 @@ static void process_blob(struct rev_info *revs, die("bad blob object"); if (obj->flags & (UNINTERESTING | SEEN)) return; - obj->flags |= SEEN; pathlen = path->len; strbuf_addstr(path, name); - show(obj, path->buf, cb_data); + if (filter_fn) + r = filter_fn(LOFS_BLOB, obj, + path->buf, &path->buf[pathlen], + filter_data); + if (r & LOFR_MARK_SEEN) + obj->flags |= SEEN; + if (r & LOFR_DO_SHOW) + show(obj, path->buf, cb_data); strbuf_setlen(path, pathlen); } @@ -69,7 +80,9 @@ static void process_tree(struct rev_info *revs, show_object_fn show, struct strbuf *base, const char *name, - void *cb_data) + void *cb_data, + filter_object_fn filter_fn, + void *filter_data) { struct object *obj = &tree->object; struct tree_desc desc; @@ -77,6 +90,7 @@ static void process_tree(struct rev_info *revs, enum interesting match = revs->diffopt.pathspec.nr == 0 ? all_entries_interesting: entry_not_interesting; int baselen = base->len; + enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW; if (!revs->tree_objects) return; @@ -90,9 +104,15 @@ static void process_tree(struct rev_info *revs, die("bad tree object %s", oid_to_hex(&obj->oid)); } - obj->flags |= SEEN; strbuf_addstr(base, name); - show(obj, base->buf, cb_data); + if (filter_fn) + r = filter_fn(LOFS_BEGIN_TREE, obj, + base->buf, &base->buf[baselen], + filter_data); + if (r & LOFR_MARK_SEEN) + obj->flags |= SEEN; + if (r & LOFR_DO_SHOW) + show(obj, base->buf, cb_data); if (base->len) strbuf_addch(base, '/'); @@ -112,7 +132,7 @@ static void process_tree(struct rev_info *revs, process_tree(revs, lookup_tree(entry.oid), show, base, entry.path, - cb_data); + cb_data, filter_fn, filter_data); else if (S_ISGITLINK(entry.mode)) process_gitlink(revs, entry.oid->hash, show, base, entry.path, @@ -121,8 +141,19 @@ static void process_tree(struct rev_info *revs, process_blob(revs, lookup_blob(entry.oid), show, base, entry.path, - cb_data); + cb_data, filter_fn, filter_data); } + + if (filter_fn) { + r = filter_fn(LOFS_END_TREE, obj, + base->buf, &base->buf[baselen], + filter_data); + if (r & LOFR_MARK_SEEN) + obj->flags |= SEEN; + if (r & LOFR_DO_SHOW) + show(obj, base->buf, cb_data); + } + strbuf_setlen(base, baselen); free_tree_buffer(tree); } @@ -183,25 +214,17 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree) add_pending_object(revs, &tree->object, ""); } -void traverse_commit_list(struct rev_info *revs, - show_commit_fn show_commit, - show_object_fn show_object, - void *data) +static void traverse_trees_and_blobs(struct rev_info *revs, + struct strbuf *base, + show_object_fn show_object, + void *show_data, + filter_object_fn filter_fn, + void *filter_data) { int i; - struct commit *commit; - struct strbuf base; - strbuf_init(&base, PATH_MAX); - while ((commit = get_revision(revs)) != NULL) { - /* - * an uninteresting boundary commit may not have its tree - * parsed yet, but we are not going to show them anyway - */ - if (commit->tree) - add_pending_tree(revs, commit->tree); - show_commit(commit, data); - } + assert(base->len == 0); + for (i = 0; i < revs->pending.nr; i++) { struct object_array_entry *pending = revs->pending.objects + i; struct object *obj = pending->item; @@ -211,24 +234,89 @@ void traverse_commit_list(struct rev_info *revs, continue; if (obj->type == OBJ_TAG) { obj->flags |= SEEN; - show_object(obj, name, data); + show_object(obj, name, show_data); continue; } if (!path) path = ""; if (obj->type == OBJ_TREE) { process_tree(revs, (struct tree *)obj, show_object, - &base, path, data); + base, path, show_data, + filter_fn, filter_data); continue; } if (obj->type == OBJ_BLOB) { process_blob(revs, (struct blob *)obj, show_object, - &base, path, data); + base, path, show_data, + filter_fn, filter_data); continue; } die("unknown pending object %s (%s)", oid_to_hex(&obj->oid), name); } object_array_clear(&revs->pending); - strbuf_release(&base); +} + +static void do_traverse(struct rev_info *revs, + show_commit_fn show_commit, + show_object_fn show_object, + void *show_data, + filter_object_fn filter_fn, + void *filter_data) +{ + struct commit *commit; + struct strbuf csp; /* callee's scratch pad */ + strbuf_init(&csp, PATH_MAX); + + while ((commit = get_revision(revs)) != NULL) { + /* + * an uninteresting boundary commit may not have its tree + * parsed yet, but we are not going to show them anyway + */ + if (commit->tree) + add_pending_tree(revs, commit->tree); + show_commit(commit, show_data); + + if (revs->tree_blobs_in_commit_order) + /* + * NEEDSWORK: Adding the tree and then flushing it here + * needs a reallocation for each commit. Can we pass the + * tree directory without allocation churn? + */ + traverse_trees_and_blobs(revs, &csp, + show_object, show_data, + filter_fn, filter_data); + } + traverse_trees_and_blobs(revs, &csp, + show_object, show_data, + filter_fn, filter_data); + strbuf_release(&csp); +} + +void traverse_commit_list(struct rev_info *revs, + show_commit_fn show_commit, + show_object_fn show_object, + void *show_data) +{ + do_traverse(revs, show_commit, show_object, show_data, NULL, NULL); +} + +void traverse_commit_list_filtered( + struct list_objects_filter_options *filter_options, + struct rev_info *revs, + show_commit_fn show_commit, + show_object_fn show_object, + void *show_data, + struct oidset *omitted) +{ + filter_object_fn filter_fn = NULL; + filter_free_fn filter_free_fn = NULL; + void *filter_data = NULL; + + filter_data = list_objects_filter__init(omitted, filter_options, + &filter_fn, &filter_free_fn); + do_traverse(revs, show_commit, show_object, show_data, + filter_fn, filter_data); + if (filter_data && filter_free_fn) + filter_free_fn(filter_data); } diff --git a/list-objects.h b/list-objects.h index 0cebf8585c..aa618d7f45 100644 --- a/list-objects.h +++ b/list-objects.h @@ -8,4 +8,15 @@ void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, voi typedef void (*show_edge_fn)(struct commit *); void mark_edges_uninteresting(struct rev_info *, show_edge_fn); -#endif +struct oidset; +struct list_objects_filter_options; + +void traverse_commit_list_filtered( + struct list_objects_filter_options *filter_options, + struct rev_info *revs, + show_commit_fn show_commit, + show_object_fn show_object, + void *show_data, + struct oidset *omitted); + +#endif /* LIST_OBJECTS_H */ diff --git a/log-tree.c b/log-tree.c index 3b904f0375..fca29d4799 100644 --- a/log-tree.c +++ b/log-tree.c @@ -94,8 +94,12 @@ static int add_ref_decoration(const char *refname, const struct object_id *oid, { struct object *obj; enum decoration_type type = DECORATION_NONE; + struct decoration_filter *filter = (struct decoration_filter *)cb_data; - assert(cb_data == NULL); + if (filter && !ref_filter_match(refname, + filter->include_ref_pattern, + filter->exclude_ref_pattern)) + return 0; if (starts_with(refname, git_replace_ref_base)) { struct object_id original_oid; @@ -148,15 +152,23 @@ static int add_graft_decoration(const struct commit_graft *graft, void *cb_data) return 0; } -void load_ref_decorations(int flags) +void load_ref_decorations(struct decoration_filter *filter, int flags) { if (!decoration_loaded) { - + if (filter) { + struct string_list_item *item; + for_each_string_list_item(item, filter->exclude_ref_pattern) { + normalize_glob_ref(item, NULL, item->string); + } + for_each_string_list_item(item, filter->include_ref_pattern) { + normalize_glob_ref(item, NULL, item->string); + } + } decoration_loaded = 1; decoration_flags = flags; - for_each_ref(add_ref_decoration, NULL); - head_ref(add_ref_decoration, NULL); - for_each_commit_graft(add_graft_decoration, NULL); + for_each_ref(add_ref_decoration, filter); + head_ref(add_ref_decoration, filter); + for_each_commit_graft(add_graft_decoration, filter); } } diff --git a/log-tree.h b/log-tree.h index 48f11fb740..deba035187 100644 --- a/log-tree.h +++ b/log-tree.h @@ -7,6 +7,10 @@ struct log_info { struct commit *commit, *parent; }; +struct decoration_filter { + struct string_list *include_ref_pattern, *exclude_ref_pattern; +}; + int parse_decorate_color_config(const char *var, const char *slot_name, const char *value); void init_log_tree_opt(struct rev_info *); int log_tree_diff_flush(struct rev_info *); @@ -24,7 +28,7 @@ void show_decorations(struct rev_info *opt, struct commit *commit); void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **extra_headers_p, int *need_8bit_cte_p); -void load_ref_decorations(int flags); +void load_ref_decorations(struct decoration_filter *filter, int flags); #define FORMAT_PATCH_NAME_MAX 64 void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *); diff --git a/merge-recursive.c b/merge-recursive.c index b48b15a6fd..0fc580d8ca 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -646,7 +646,7 @@ static int remove_file(struct merge_options *o, int clean, if (ignore_case) { struct cache_entry *ce; ce = cache_file_exists(path, strlen(path), ignore_case); - if (ce && ce_stage(ce) == 0) + if (ce && ce_stage(ce) == 0 && strcmp(path, ce->name)) return 0; } if (remove_path(path)) @@ -1901,8 +1901,9 @@ static int process_entry(struct merge_options *o, oid = b_oid; conf = _("directory/file"); } - if (dir_in_way(path, !o->call_depth, - S_ISGITLINK(a_mode))) { + if (dir_in_way(path, + !o->call_depth && !S_ISGITLINK(a_mode), + 0)) { char *new_path = unique_path(o, path, add_branch); clean_merge = 0; output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " @@ -1951,6 +1952,13 @@ int merge_trees(struct merge_options *o, } if (oid_eq(&common->object.oid, &merge->object.oid)) { + struct strbuf sb = STRBUF_INIT; + + if (!o->call_depth && index_has_changes(&sb)) { + err(o, _("Dirty index: cannot merge (dirty: %s)"), + sb.buf); + return 0; + } output(o, 0, _("Already up to date!")); *result = head; return 1; @@ -2081,7 +2089,7 @@ int merge_recursive(struct merge_options *o, /* if there is no common ancestor, use an empty tree */ struct tree *tree; - tree = lookup_tree(&empty_tree_oid); + tree = lookup_tree(the_hash_algo->empty_tree); merged_common_ancestors = make_virtual_commit(tree, "ancestor"); } @@ -2252,6 +2260,8 @@ int parse_merge_opt(struct merge_options *o, const char *s) DIFF_XDL_SET(o, IGNORE_WHITESPACE); else if (!strcmp(s, "ignore-space-at-eol")) DIFF_XDL_SET(o, IGNORE_WHITESPACE_AT_EOL); + else if (!strcmp(s, "ignore-cr-at-eol")) + DIFF_XDL_SET(o, IGNORE_CR_AT_EOL); else if (!strcmp(s, "renormalize")) o->renormalize = 1; else if (!strcmp(s, "no-renormalize")) @@ -1,4 +1,6 @@ #include "cache.h" +#include "diff.h" +#include "diffcore.h" #include "lockfile.h" #include "commit.h" #include "run-command.h" @@ -15,6 +17,37 @@ static const char *merge_argument(struct commit *commit) return EMPTY_TREE_SHA1_HEX; } +int index_has_changes(struct strbuf *sb) +{ + struct object_id head; + int i; + + if (!get_oid_tree("HEAD", &head)) { + struct diff_options opt; + + diff_setup(&opt); + opt.flags.exit_with_status = 1; + if (!sb) + opt.flags.quick = 1; + do_diff_cache(&head, &opt); + diffcore_std(&opt); + for (i = 0; sb && i < diff_queued_diff.nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path); + } + diff_flush(&opt); + return opt.flags.has_changes != 0; + } else { + for (i = 0; sb && i < active_nr; i++) { + if (i) + strbuf_addch(sb, ' '); + strbuf_addstr(sb, active_cache[i]->name); + } + return !!active_nr; + } +} + int try_merge_command(const char *strategy, size_t xopts_nr, const char **xopts, struct commit_list *common, const char *head_arg, struct commit_list *remotes) diff --git a/notes-merge.c b/notes-merge.c index 4a83b0ebd5..0f6573cb17 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -595,7 +595,7 @@ int notes_merge(struct notes_merge_options *o, bases = get_merge_bases(local, remote); if (!bases) { base_oid = &null_oid; - base_tree_oid = &empty_tree_oid; + base_tree_oid = the_hash_algo->empty_tree; if (o->verbosity >= 4) printf("No merge base found; doing history-less merge\n"); } else if (!bases->next) { @@ -38,6 +38,7 @@ struct object_array { * http-push.c: 16-----19 * commit.c: 16-----19 * sha1_name.c: 20 + * list-objects-filter.c: 21 * builtin/fsck.c: 0--3 */ #define FLAG_BITS 27 @@ -65,4 +65,26 @@ extern void *oidmap_put(struct oidmap *map, void *entry); */ extern void *oidmap_remove(struct oidmap *map, const struct object_id *key); + +struct oidmap_iter { + struct hashmap_iter h_iter; +}; + +static inline void oidmap_iter_init(struct oidmap *map, struct oidmap_iter *iter) +{ + hashmap_iter_init(&map->map, &iter->h_iter); +} + +static inline void *oidmap_iter_next(struct oidmap_iter *iter) +{ + return hashmap_iter_next(&iter->h_iter); +} + +static inline void *oidmap_iter_first(struct oidmap *map, + struct oidmap_iter *iter) +{ + oidmap_iter_init(map, iter); + return oidmap_iter_next(iter); +} + #endif @@ -24,6 +24,16 @@ int oidset_insert(struct oidset *set, const struct object_id *oid) return 0; } +int oidset_remove(struct oidset *set, const struct object_id *oid) +{ + struct oidmap_entry *entry; + + entry = oidmap_remove(&set->map, oid); + free(entry); + + return (entry != NULL); +} + void oidset_clear(struct oidset *set) { oidmap_free(&set->map, 1); @@ -24,6 +24,12 @@ struct oidset { #define OIDSET_INIT { OIDMAP_INIT } + +static inline void oidset_init(struct oidset *set, size_t initial_size) +{ + return oidmap_init(&set->map, initial_size); +} + /** * Returns true iff `set` contains `oid`. */ @@ -39,9 +45,39 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid); int oidset_insert(struct oidset *set, const struct object_id *oid); /** + * Remove the oid from the set. + * + * Returns 1 if the oid was present in the set, 0 otherwise. + */ +int oidset_remove(struct oidset *set, const struct object_id *oid); + +/** * Remove all entries from the oidset, freeing any resources associated with * it. */ void oidset_clear(struct oidset *set); +struct oidset_iter { + struct oidmap_iter m_iter; +}; + +static inline void oidset_iter_init(struct oidset *set, + struct oidset_iter *iter) +{ + oidmap_iter_init(&set->map, &iter->m_iter); +} + +static inline struct object_id *oidset_iter_next(struct oidset_iter *iter) +{ + struct oidmap_entry *e = oidmap_iter_next(&iter->m_iter); + return e ? &e->oid : NULL; +} + +static inline struct object_id *oidset_iter_first(struct oidset *set, + struct oidset_iter *iter) +{ + oidset_iter_init(set, iter); + return oidset_iter_next(iter); +} + #endif /* OIDSET_H */ @@ -4,53 +4,144 @@ struct repository; /* - * Return a statically allocated filename, either generically (mkpath), in - * the repository directory (git_path), or in a submodule's repository - * directory (git_path_submodule). In all cases, note that the result - * may be overwritten by another call to _any_ of the functions. Consider - * using the safer "dup" or "strbuf" formats below (in some cases, the - * unsafe versions have already been removed). + * The result to all functions which return statically allocated memory may be + * overwritten by another call to _any_ one of these functions. Consider using + * the safer variants which operate on strbufs or return allocated memory. */ -extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +/* + * Return a statically allocated path. + */ +extern const char *mkpath(const char *fmt, ...) + __attribute__((format (printf, 1, 2))); + +/* + * Return a path. + */ +extern char *mkpathdup(const char *fmt, ...) + __attribute__((format (printf, 1, 2))); + +/* + * Construct a path and place the result in the provided buffer `buf`. + */ extern char *mksnpath(char *buf, size_t n, const char *fmt, ...) __attribute__((format (printf, 3, 4))); -extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) - __attribute__((format (printf, 2, 3))); + +/* + * The `git_common_path` family of functions will construct a path into a + * repository's common git directory, which is shared by all worktrees. + */ + +/* + * Constructs a path into the common git directory of repository `repo` and + * append it in the provided buffer `sb`. + */ extern void strbuf_git_common_path(struct strbuf *sb, const struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 3, 4))); -extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) - __attribute__((format (printf, 2, 3))); -extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path, - const char *fmt, ...) - __attribute__((format (printf, 3, 4))); -extern char *git_pathdup(const char *fmt, ...) - __attribute__((format (printf, 1, 2))); -extern char *mkpathdup(const char *fmt, ...) + +/* + * Return a statically allocated path into the main repository's + * (the_repository) common git directory. + */ +extern const char *git_common_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern char *git_pathdup_submodule(const char *path, const char *fmt, ...) - __attribute__((format (printf, 2, 3))); + +/* + * The `git_path` family of functions will construct a path into a repository's + * git directory. + * + * These functions will perform adjustments to the resultant path to account + * for special paths which are either considered common among worktrees (e.g. + * paths into the object directory) or have been explicitly set via an + * environment variable or config (e.g. path to the index file). + * + * For an exhaustive list of the adjustments made look at `common_list` and + * `adjust_git_path` in path.c. + */ + +/* + * Return a path into the git directory of repository `repo`. + */ extern char *repo_git_path(const struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 2, 3))); + +/* + * Construct a path into the git directory of repository `repo` and append it + * to the provided buffer `sb`. + */ extern void strbuf_repo_git_path(struct strbuf *sb, const struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 3, 4))); +/* + * Return a statically allocated path into the main repository's + * (the_repository) git directory. + */ +extern const char *git_path(const char *fmt, ...) + __attribute__((format (printf, 1, 2))); + +/* + * Return a path into the main repository's (the_repository) git directory. + */ +extern char *git_pathdup(const char *fmt, ...) + __attribute__((format (printf, 1, 2))); + +/* + * Construct a path into the main repository's (the_repository) git directory + * and place it in the provided buffer `buf`, the contents of the buffer will + * be overridden. + */ +extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); + +/* + * Construct a path into the main repository's (the_repository) git directory + * and append it to the provided buffer `sb`. + */ +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); + +/* + * Return a path into the worktree of repository `repo`. + * + * If the repository doesn't have a worktree NULL is returned. + */ extern char *repo_worktree_path(const struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 2, 3))); + +/* + * Construct a path into the worktree of repository `repo` and append it + * to the provided buffer `sb`. + * + * If the repository doesn't have a worktree nothing will be appended to `sb`. + */ extern void strbuf_repo_worktree_path(struct strbuf *sb, const struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 3, 4))); +/* + * Return a path into a submodule's git directory located at `path`. `path` + * must only reference a submodule of the main repository (the_repository). + */ +extern char *git_pathdup_submodule(const char *path, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); + +/* + * Construct a path into a submodule's git directory located at `path` and + * append it to the provided buffer `sb`. `path` must only reference a + * submodule of the main repository (the_repository). + */ +extern int strbuf_git_path_submodule(struct strbuf *sb, const char *path, + const char *fmt, ...) + __attribute__((format (printf, 3, 4))); + extern void report_linked_checkout_garbage(void); /* diff --git a/pathspec.h b/pathspec.h index 6420d1080a..099a170c2e 100644 --- a/pathspec.h +++ b/pathspec.h @@ -24,6 +24,7 @@ struct pathspec { int nr; unsigned int has_wildcard:1; unsigned int recursive:1; + unsigned int recurse_submodules:1; unsigned magic; int max_depth; struct pathspec_item { diff --git a/perl/Git/Packet.pm b/perl/Git/Packet.pm index 255b28c098..b75738bed4 100644 --- a/perl/Git/Packet.pm +++ b/perl/Git/Packet.pm @@ -17,7 +17,7 @@ our @EXPORT = qw( packet_compare_lists packet_bin_read packet_txt_read - packet_required_key_val_read + packet_key_val_read packet_bin_write packet_txt_write packet_flush @@ -68,28 +68,33 @@ sub packet_bin_read { sub remove_final_lf_or_die { my $buf = shift; - unless ( $buf =~ s/\n$// ) { - die "A non-binary line MUST be terminated by an LF.\n" - . "Received: '$buf'"; + if ( $buf =~ s/\n$// ) { + return $buf; } - return $buf; + die "A non-binary line MUST be terminated by an LF.\n" + . "Received: '$buf'"; } sub packet_txt_read { my ( $res, $buf ) = packet_bin_read(); - unless ( $res == -1 or $buf eq '' ) { + if ( $res != -1 and $buf ne '' ) { $buf = remove_final_lf_or_die($buf); } return ( $res, $buf ); } -sub packet_required_key_val_read { +# Read a text packet, expecting that it is in the form "key=value" for +# the given $key. An EOF does not trigger any error and is reported +# back to the caller (like packet_txt_read() does). Die if the "key" +# part of "key=value" does not match the given $key, or the value part +# is empty. +sub packet_key_val_read { my ( $key ) = @_; my ( $res, $buf ) = packet_txt_read(); - unless ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) { - die "bad $key: '$buf'"; + if ( $res == -1 or ( $buf =~ s/^$key=// and $buf ne '' ) ) { + return ( $res, $buf ); } - return ( $res, $buf ); + die "bad $key: '$buf'"; } sub packet_bin_write { diff --git a/pkt-line.c b/pkt-line.c index 93ea311443..2827ca772a 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -188,6 +188,12 @@ static int packet_write_gently(const int fd_out, const char *buf, size_t size) return 0; } +void packet_write(int fd_out, const char *buf, size_t size) +{ + if (packet_write_gently(fd_out, buf, size)) + die_errno("packet write failed"); +} + void packet_buf_write(struct strbuf *buf, const char *fmt, ...) { va_list args; diff --git a/pkt-line.h b/pkt-line.h index 66ef610fc4..3dad583e2d 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -22,6 +22,7 @@ void packet_flush(int fd); void packet_write_fmt(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); void packet_buf_flush(struct strbuf *buf); +void packet_write(int fd_out, const char *buf, size_t size); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); int packet_flush_gently(int fd); int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); @@ -1186,11 +1186,11 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(NULL, DECORATE_SHORT_REFS); format_decorations(sb, commit, c->auto_color); return 1; case 'D': - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(NULL, DECORATE_SHORT_REFS); format_decorations_extended(sb, commit, c->auto_color, "", ", ", ""); return 1; case 'g': /* reflog info */ diff --git a/pretty.h b/pretty.h new file mode 100644 index 0000000000..5c85d94e33 --- /dev/null +++ b/pretty.h @@ -0,0 +1,131 @@ +#ifndef PRETTY_H +#define PRETTY_H + +struct commit; + +/* Commit formats */ +enum cmit_fmt { + CMIT_FMT_RAW, + CMIT_FMT_MEDIUM, + CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM, + CMIT_FMT_SHORT, + CMIT_FMT_FULL, + CMIT_FMT_FULLER, + CMIT_FMT_ONELINE, + CMIT_FMT_EMAIL, + CMIT_FMT_MBOXRD, + CMIT_FMT_USERFORMAT, + + CMIT_FMT_UNSPECIFIED +}; + +struct pretty_print_context { + /* + * Callers should tweak these to change the behavior of pp_* functions. + */ + enum cmit_fmt fmt; + int abbrev; + const char *after_subject; + int preserve_subject; + struct date_mode date_mode; + unsigned date_mode_explicit:1; + int print_email_subject; + int expand_tabs_in_log; + int need_8bit_cte; + char *notes_message; + struct reflog_walk_info *reflog_info; + struct rev_info *rev; + const char *output_encoding; + struct string_list *mailmap; + int color; + struct ident_split *from_ident; + + /* + * Fields below here are manipulated internally by pp_* functions and + * should not be counted on by callers. + */ + struct string_list in_body_headers; + int graph_width; +}; + +/* Check whether commit format is mail. */ +static inline int cmit_fmt_is_mail(enum cmit_fmt fmt) +{ + return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD); +} + +struct userformat_want { + unsigned notes:1; +}; + +/* Set the flag "w->notes" if there is placeholder %N in "fmt". */ +void userformat_find_requirements(const char *fmt, struct userformat_want *w); + +/* + * Shortcut for invoking pretty_print_commit if we do not have any context. + * Context would be set empty except "fmt". + */ +void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit, + struct strbuf *sb); + +/* + * Get information about user and date from "line", format it and + * put it into "sb". + * Format of "line" must be readable for split_ident_line function. + * The resulting format is "what: name <email> date". + */ +void pp_user_info(struct pretty_print_context *pp, const char *what, + struct strbuf *sb, const char *line, + const char *encoding); + +/* + * Format title line of commit message taken from "msg_p" and + * put it into "sb". + * First line of "msg_p" is also affected. + */ +void pp_title_line(struct pretty_print_context *pp, const char **msg_p, + struct strbuf *sb, const char *encoding, + int need_8bit_cte); + +/* + * Get current state of commit message from "msg_p" and continue formatting + * by adding indentation and '>' signs. Put result into "sb". + */ +void pp_remainder(struct pretty_print_context *pp, const char **msg_p, + struct strbuf *sb, int indent); + +/* + * Create a text message about commit using given "format" and "context". + * Put the result to "sb". + * Please use this function for custom formats. + */ +void format_commit_message(const struct commit *commit, + const char *format, struct strbuf *sb, + const struct pretty_print_context *context); + +/* + * Parse given arguments from "arg", check it for correctness and + * fill struct rev_info. + */ +void get_commit_format(const char *arg, struct rev_info *); + +/* + * Make a commit message with all rules from given "pp" + * and put it into "sb". + * Please use this function if you have a context (candidate for "pp"). + */ +void pretty_print_commit(struct pretty_print_context *pp, + const struct commit *commit, + struct strbuf *sb); + +/* + * Change line breaks in "msg" to "line_separator" and put it into "sb". + * Return "msg" itself. + */ +const char *format_subject(struct strbuf *sb, const char *msg, + const char *line_separator); + +/* Check if "cmit_fmt" will produce an empty output. */ +int commit_format_is_empty(enum cmit_fmt); + +#endif /* PRETTY_H */ diff --git a/progress.c b/progress.c index 289678d43d..5a99c9fbf0 100644 --- a/progress.c +++ b/progress.c @@ -30,11 +30,10 @@ struct throughput { struct progress { const char *title; - int last_value; - unsigned total; + uint64_t last_value; + uint64_t total; unsigned last_percent; unsigned delay; - unsigned delayed_percent_threshold; struct throughput *throughput; uint64_t start_ns; }; @@ -79,24 +78,12 @@ static int is_foreground_fd(int fd) return tpgrp < 0 || tpgrp == getpgid(0); } -static int display(struct progress *progress, unsigned n, const char *done) +static int display(struct progress *progress, uint64_t n, const char *done) { const char *eol, *tp; - if (progress->delay) { - if (!progress_update || --progress->delay) - return 0; - if (progress->total) { - unsigned percent = n * 100 / progress->total; - if (percent > progress->delayed_percent_threshold) { - /* inhibit this progress report entirely */ - clear_progress_signal(); - progress->delay = -1; - progress->total = 0; - return 0; - } - } - } + if (progress->delay && (!progress_update || --progress->delay)) + return 0; progress->last_value = n; tp = (progress->throughput) ? progress->throughput->display.buf : ""; @@ -106,9 +93,10 @@ static int display(struct progress *progress, unsigned n, const char *done) if (percent != progress->last_percent || progress_update) { progress->last_percent = percent; if (is_foreground_fd(fileno(stderr)) || done) { - fprintf(stderr, "%s: %3u%% (%u/%u)%s%s", - progress->title, percent, n, - progress->total, tp, eol); + fprintf(stderr, "%s: %3u%% (%"PRIuMAX"/%"PRIuMAX")%s%s", + progress->title, percent, + (uintmax_t)n, (uintmax_t)progress->total, + tp, eol); fflush(stderr); } progress_update = 0; @@ -116,8 +104,8 @@ static int display(struct progress *progress, unsigned n, const char *done) } } else if (progress_update) { if (is_foreground_fd(fileno(stderr)) || done) { - fprintf(stderr, "%s: %u%s%s", - progress->title, n, tp, eol); + fprintf(stderr, "%s: %"PRIuMAX"%s%s", + progress->title, (uintmax_t)n, tp, eol); fflush(stderr); } progress_update = 0; @@ -127,7 +115,7 @@ static int display(struct progress *progress, unsigned n, const char *done) return 0; } -static void throughput_string(struct strbuf *buf, off_t total, +static void throughput_string(struct strbuf *buf, uint64_t total, unsigned int rate) { strbuf_reset(buf); @@ -138,7 +126,7 @@ static void throughput_string(struct strbuf *buf, off_t total, strbuf_addstr(buf, "/s"); } -void display_throughput(struct progress *progress, off_t total) +void display_throughput(struct progress *progress, uint64_t total) { struct throughput *tp; uint64_t now_ns; @@ -200,13 +188,13 @@ void display_throughput(struct progress *progress, off_t total) display(progress, progress->last_value, NULL); } -int display_progress(struct progress *progress, unsigned n) +int display_progress(struct progress *progress, uint64_t n) { return progress ? display(progress, n, NULL) : 0; } -static struct progress *start_progress_delay(const char *title, unsigned total, - unsigned percent_threshold, unsigned delay) +static struct progress *start_progress_delay(const char *title, uint64_t total, + unsigned delay) { struct progress *progress = malloc(sizeof(*progress)); if (!progress) { @@ -219,7 +207,6 @@ static struct progress *start_progress_delay(const char *title, unsigned total, progress->total = total; progress->last_value = -1; progress->last_percent = -1; - progress->delayed_percent_threshold = percent_threshold; progress->delay = delay; progress->throughput = NULL; progress->start_ns = getnanotime(); @@ -227,14 +214,14 @@ static struct progress *start_progress_delay(const char *title, unsigned total, return progress; } -struct progress *start_delayed_progress(const char *title, unsigned total) +struct progress *start_delayed_progress(const char *title, uint64_t total) { - return start_progress_delay(title, total, 0, 2); + return start_progress_delay(title, total, 2); } -struct progress *start_progress(const char *title, unsigned total) +struct progress *start_progress(const char *title, uint64_t total) { - return start_progress_delay(title, total, 0, 0); + return start_progress_delay(title, total, 0); } void stop_progress(struct progress **p_progress) diff --git a/progress.h b/progress.h index 6392b63371..70a4d4a0d6 100644 --- a/progress.h +++ b/progress.h @@ -3,10 +3,10 @@ struct progress; -void display_throughput(struct progress *progress, off_t total); -int display_progress(struct progress *progress, unsigned n); -struct progress *start_progress(const char *title, unsigned total); -struct progress *start_delayed_progress(const char *title, unsigned total); +void display_throughput(struct progress *progress, uint64_t total); +int display_progress(struct progress *progress, uint64_t n); +struct progress *start_progress(const char *title, uint64_t total); +struct progress *start_delayed_progress(const char *title, uint64_t total); void stop_progress(struct progress **progress); void stop_progress_msg(struct progress **progress, const char *msg); diff --git a/protocol.c b/protocol.c new file mode 100644 index 0000000000..43012b7eb6 --- /dev/null +++ b/protocol.c @@ -0,0 +1,79 @@ +#include "cache.h" +#include "config.h" +#include "protocol.h" + +static enum protocol_version parse_protocol_version(const char *value) +{ + if (!strcmp(value, "0")) + return protocol_v0; + else if (!strcmp(value, "1")) + return protocol_v1; + else + return protocol_unknown_version; +} + +enum protocol_version get_protocol_version_config(void) +{ + const char *value; + if (!git_config_get_string_const("protocol.version", &value)) { + enum protocol_version version = parse_protocol_version(value); + + if (version == protocol_unknown_version) + die("unknown value for config 'protocol.version': %s", + value); + + return version; + } + + return protocol_v0; +} + +enum protocol_version determine_protocol_version_server(void) +{ + const char *git_protocol = getenv(GIT_PROTOCOL_ENVIRONMENT); + enum protocol_version version = protocol_v0; + + /* + * Determine which protocol version the client has requested. Since + * multiple 'version' keys can be sent by the client, indicating that + * the client is okay to speak any of them, select the greatest version + * that the client has requested. This is due to the assumption that + * the most recent protocol version will be the most state-of-the-art. + */ + if (git_protocol) { + struct string_list list = STRING_LIST_INIT_DUP; + const struct string_list_item *item; + string_list_split(&list, git_protocol, ':', -1); + + for_each_string_list_item(item, &list) { + const char *value; + enum protocol_version v; + + if (skip_prefix(item->string, "version=", &value)) { + v = parse_protocol_version(value); + if (v > version) + version = v; + } + } + + string_list_clear(&list, 0); + } + + return version; +} + +enum protocol_version determine_protocol_version_client(const char *server_response) +{ + enum protocol_version version = protocol_v0; + + if (skip_prefix(server_response, "version ", &server_response)) { + version = parse_protocol_version(server_response); + + if (version == protocol_unknown_version) + die("server is speaking an unknown protocol"); + if (version == protocol_v0) + die("protocol error: server explicitly said version 0"); + } + + return version; +} diff --git a/protocol.h b/protocol.h new file mode 100644 index 0000000000..1b2bc94a8d --- /dev/null +++ b/protocol.h @@ -0,0 +1,33 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +enum protocol_version { + protocol_unknown_version = -1, + protocol_v0 = 0, + protocol_v1 = 1, +}; + +/* + * Used by a client to determine which protocol version to request be used when + * communicating with a server, reflecting the configured value of the + * 'protocol.version' config. If unconfigured, a value of 'protocol_v0' is + * returned. + */ +extern enum protocol_version get_protocol_version_config(void); + +/* + * Used by a server to determine which protocol version should be used based on + * a client's request, communicated via the 'GIT_PROTOCOL' environment variable + * by setting appropriate values for the key 'version'. If a client doesn't + * request a particular protocol version, a default of 'protocol_v0' will be + * used. + */ +extern enum protocol_version determine_protocol_version_server(void); + +/* + * Used by a client to determine which protocol version the server is speaking + * based on the server's initial response. + */ +extern enum protocol_version determine_protocol_version_client(const char *server_response); + +#endif /* PROTOCOL_H */ diff --git a/read-cache.c b/read-cache.c index fbb4967c81..2eb81a66b9 100644 --- a/read-cache.c +++ b/read-cache.c @@ -641,13 +641,17 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, { int size, namelen, was_same; mode_t st_mode = st->st_mode; - struct cache_entry *ce, *alias; + struct cache_entry *ce, *alias = NULL; unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE|CE_MATCH_RACY_IS_DIRTY; int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND); int pretend = flags & ADD_CACHE_PRETEND; int intent_only = flags & ADD_CACHE_INTENT; int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE| (intent_only ? ADD_CACHE_NEW_ONLY : 0)); + int newflags = HASH_WRITE_OBJECT; + + if (flags & HASH_RENORMALIZE) + newflags |= HASH_RENORMALIZE; if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode)) return error("%s: can only add regular files, symbolic links or git-directories", path); @@ -688,19 +692,23 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, if (ignore_case) { adjust_dirname_case(istate, ce->name); } + if (!(flags & HASH_RENORMALIZE)) { + alias = index_file_exists(istate, ce->name, + ce_namelen(ce), ignore_case); + if (alias && + !ce_stage(alias) && + !ie_match_stat(istate, alias, st, ce_option)) { + /* Nothing changed, really */ + if (!S_ISGITLINK(alias->ce_mode)) + ce_mark_uptodate(alias); + alias->ce_flags |= CE_ADDED; - alias = index_file_exists(istate, ce->name, ce_namelen(ce), ignore_case); - if (alias && !ce_stage(alias) && !ie_match_stat(istate, alias, st, ce_option)) { - /* Nothing changed, really */ - if (!S_ISGITLINK(alias->ce_mode)) - ce_mark_uptodate(alias); - alias->ce_flags |= CE_ADDED; - - free(ce); - return 0; + free(ce); + return 0; + } } if (!intent_only) { - if (index_path(&ce->oid, path, st, HASH_WRITE_OBJECT)) { + if (index_path(&ce->oid, path, st, newflags)) { free(ce); return error("unable to index file %s", path); } @@ -242,6 +242,50 @@ int ref_exists(const char *refname) return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, NULL); } +static int match_ref_pattern(const char *refname, + const struct string_list_item *item) +{ + int matched = 0; + if (item->util == NULL) { + if (!wildmatch(item->string, refname, 0)) + matched = 1; + } else { + const char *rest; + if (skip_prefix(refname, item->string, &rest) && + (!*rest || *rest == '/')) + matched = 1; + } + return matched; +} + +int ref_filter_match(const char *refname, + const struct string_list *include_patterns, + const struct string_list *exclude_patterns) +{ + struct string_list_item *item; + + if (exclude_patterns && exclude_patterns->nr) { + for_each_string_list_item(item, exclude_patterns) { + if (match_ref_pattern(refname, item)) + return 0; + } + } + + if (include_patterns && include_patterns->nr) { + int found = 0; + for_each_string_list_item(item, include_patterns) { + if (match_ref_pattern(refname, item)) { + found = 1; + break; + } + } + + if (!found) + return 0; + } + return 1; +} + static int filter_refs(const char *refname, const struct object_id *oid, int flags, void *data) { @@ -369,6 +413,27 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) return ret; } +void normalize_glob_ref(struct string_list_item *item, const char *prefix, + const char *pattern) +{ + struct strbuf normalized_pattern = STRBUF_INIT; + + if (*pattern == '/') + BUG("pattern must not start with '/'"); + + if (prefix) { + strbuf_addstr(&normalized_pattern, prefix); + } + else if (!starts_with(pattern, "refs/")) + strbuf_addstr(&normalized_pattern, "refs/"); + strbuf_addstr(&normalized_pattern, pattern); + strbuf_strip_suffix(&normalized_pattern, "/"); + + item->string = strbuf_detach(&normalized_pattern, NULL); + item->util = has_glob_specials(pattern) ? NULL : item->string; + strbuf_release(&normalized_pattern); +} + int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data) { @@ -312,6 +312,30 @@ int for_each_namespaced_ref(each_ref_fn fn, void *cb_data); int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data); int for_each_rawref(each_ref_fn fn, void *cb_data); +/* + * Normalizes partial refs to their fully qualified form. + * Will prepend <prefix> to the <pattern> if it doesn't start with 'refs/'. + * <prefix> will default to 'refs/' if NULL. + * + * item.string will be set to the result. + * item.util will be set to NULL if <pattern> contains glob characters, or + * non-NULL if it doesn't. + */ +void normalize_glob_ref(struct string_list_item *item, const char *prefix, + const char *pattern); + +/* + * Returns 0 if refname matches any of the exclude_patterns, or if it doesn't + * match any of the include_patterns. Returns 1 otherwise. + * + * If pattern list is NULL or empty, matching against that list is skipped. + * This has the effect of matching everything by default, unless the user + * specifies rules otherwise. + */ +int ref_filter_match(const char *refname, + const struct string_list *include_patterns, + const struct string_list *exclude_patterns); + static inline const char *has_glob_specials(const char *pattern) { return strpbrk(pattern, "?*["); diff --git a/repository.c b/repository.c index bb2fae5446..998413b8bb 100644 --- a/repository.c +++ b/repository.c @@ -5,7 +5,7 @@ /* The main repository */ static struct repository the_repo = { - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, 0, 0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &the_index, NULL, 0, 0 }; struct repository *the_repository = &the_repo; @@ -64,6 +64,11 @@ void repo_set_gitdir(struct repository *repo, const char *path) free(old_gitdir); } +void repo_set_hash_algo(struct repository *repo, int hash_algo) +{ + repo->hash_algo = &hash_algos[hash_algo]; +} + /* * Attempt to resolve and set the provided 'gitdir' for repository 'repo'. * Return 0 upon success and a non-zero value upon failure. @@ -136,6 +141,8 @@ int repo_init(struct repository *repo, const char *gitdir, const char *worktree) if (read_and_verify_repository_format(&format, repo->commondir)) goto error; + repo_set_hash_algo(repo, format.hash_algo); + if (worktree) repo_set_worktree(repo, worktree); diff --git a/repository.h b/repository.h index 7f5e24a0a2..0329e40c7f 100644 --- a/repository.h +++ b/repository.h @@ -4,6 +4,7 @@ struct config_set; struct index_state; struct submodule_cache; +struct git_hash_algo; struct repository { /* Environment */ @@ -67,6 +68,9 @@ struct repository { */ struct index_state *index; + /* Repository's current hash algorithm, as serialized on disk. */ + const struct git_hash_algo *hash_algo; + /* Configurations */ /* * Bit used during initialization to indicate if repository state (like @@ -86,6 +90,7 @@ extern struct repository *the_repository; extern void repo_set_gitdir(struct repository *repo, const char *path); extern void repo_set_worktree(struct repository *repo, const char *path); +extern void repo_set_hash_algo(struct repository *repo, int algo); extern int repo_init(struct repository *repo, const char *gitdir, const char *worktree); extern int repo_submodule_init(struct repository *submodule, struct repository *superproject, diff --git a/revision.c b/revision.c index e2e691dd5a..72f2b4572e 100644 --- a/revision.c +++ b/revision.c @@ -1832,7 +1832,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->simplify_by_decoration = 1; revs->limited = 1; revs->prune = 1; - load_ref_decorations(DECORATE_SHORT_REFS); + load_ref_decorations(NULL, DECORATE_SHORT_REFS); } else if (!strcmp(arg, "--date-order")) { revs->sort_order = REV_SORT_BY_COMMIT_DATE; revs->topo_order = 1; @@ -1855,6 +1855,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->dense = 0; } else if (!strcmp(arg, "--show-all")) { revs->show_all = 1; + } else if (!strcmp(arg, "--in-commit-order")) { + revs->tree_blobs_in_commit_order = 1; } else if (!strcmp(arg, "--remove-empty")) { revs->remove_empty_trees = 1; } else if (!strcmp(arg, "--merges")) { diff --git a/revision.h b/revision.h index 54761200ad..19dc9bdddb 100644 --- a/revision.h +++ b/revision.h @@ -4,7 +4,7 @@ #include "parse-options.h" #include "grep.h" #include "notes.h" -#include "commit.h" +#include "pretty.h" #include "diff.h" /* Remember to update object flag allocation in object.h */ @@ -121,7 +121,8 @@ struct rev_info { bisect:1, ancestry_path:1, first_parent_only:1, - line_level_traverse:1; + line_level_traverse:1, + tree_blobs_in_commit_order:1; /* Diff flags */ unsigned int diff:1, diff --git a/send-pack.c b/send-pack.c index a8cc6b266e..2112d3b27a 100644 --- a/send-pack.c +++ b/send-pack.c @@ -58,35 +58,25 @@ static int pack_objects(int fd, struct ref *refs, struct oid_array *extra, struc * the revision parameters to it via its stdin and * let its stdout go back to the other end. */ - const char *argv[] = { - "pack-objects", - "--all-progress-implied", - "--revs", - "--stdout", - NULL, - NULL, - NULL, - NULL, - NULL, - NULL, - }; struct child_process po = CHILD_PROCESS_INIT; FILE *po_in; int i; int rc; - i = 4; + argv_array_push(&po.args, "pack-objects"); + argv_array_push(&po.args, "--all-progress-implied"); + argv_array_push(&po.args, "--revs"); + argv_array_push(&po.args, "--stdout"); if (args->use_thin_pack) - argv[i++] = "--thin"; + argv_array_push(&po.args, "--thin"); if (args->use_ofs_delta) - argv[i++] = "--delta-base-offset"; + argv_array_push(&po.args, "--delta-base-offset"); if (args->quiet || !args->progress) - argv[i++] = "-q"; + argv_array_push(&po.args, "-q"); if (args->progress) - argv[i++] = "--progress"; + argv_array_push(&po.args, "--progress"); if (is_repository_shallow()) - argv[i++] = "--shallow"; - po.argv = argv; + argv_array_push(&po.args, "--shallow"); po.in = -1; po.out = args->stateless_rpc ? -1 : fd; po.git_cmd = 1; diff --git a/sequencer.c b/sequencer.c index 19dd575ed9..4d3f60594c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -347,7 +347,7 @@ static int read_oneliner(struct strbuf *buf, static struct tree *empty_tree(void) { - return lookup_tree(&empty_tree_oid); + return lookup_tree(the_hash_algo->empty_tree); } static int error_dirty_index(struct replay_opts *opts) @@ -438,7 +438,8 @@ static int do_recursive_merge(struct commit *base, struct commit *next, char **xopt; static struct lock_file index_lock; - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (hold_locked_index(&index_lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; read_cache(); @@ -448,6 +449,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, o.branch2 = next ? next_label : "(empty tree)"; if (is_rebase_i(opts)) o.buffer_output = 2; + o.show_rename_progress = 1; head_tree = parse_tree_indirect(head); next_tree = next ? next->tree : empty_tree(); @@ -462,6 +464,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, if (is_rebase_i(opts) && clean <= 0) fputs(o.obuf.buf, stdout); strbuf_release(&o.obuf); + diff_warn_rename_limit("merge.renamelimit", o.needed_rename_limit, 0); if (clean < 0) return clean; @@ -490,7 +493,7 @@ static int is_index_unchanged(void) struct commit *head_commit; if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) - return error(_("could not resolve HEAD commit\n")); + return error(_("could not resolve HEAD commit")); head_commit = lookup_commit(&head_oid); @@ -510,7 +513,7 @@ static int is_index_unchanged(void) if (!cache_tree_fully_valid(active_cache_tree)) if (cache_tree_update(&the_index, 0)) - return error(_("unable to update cache tree\n")); + return error(_("unable to update cache tree")); return !oidcmp(&active_cache_tree->oid, &head_commit->tree->object.oid); @@ -696,16 +699,16 @@ static int is_original_commit_empty(struct commit *commit) const struct object_id *ptree_oid; if (parse_commit(commit)) - return error(_("could not parse commit %s\n"), + return error(_("could not parse commit %s"), oid_to_hex(&commit->object.oid)); if (commit->parents) { struct commit *parent = commit->parents->item; if (parse_commit(parent)) - return error(_("could not parse parent commit %s\n"), + return error(_("could not parse parent commit %s"), oid_to_hex(&parent->object.oid)); ptree_oid = &parent->tree->object.oid; } else { - ptree_oid = &empty_tree_oid; /* commit is root */ + ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } return !oidcmp(ptree_oid, &commit->tree->object.oid); @@ -794,6 +797,13 @@ static const char *command_to_string(const enum todo_command command) die("Unknown command: %d", command); } +static char command_to_char(const enum todo_command command) +{ + if (command < TODO_COMMENT && todo_command_info[command].c) + return todo_command_info[command].c; + return comment_line_char; +} + static int is_noop(const enum todo_command command) { return TODO_NOOP <= command; @@ -958,7 +968,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, } else { unborn = get_oid("HEAD", &head); if (unborn) - oidcpy(&head, &empty_tree_oid); + oidcpy(&head, the_hash_algo->empty_tree); if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", NULL, 0)) return error_dirty_index(opts); @@ -1003,9 +1013,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit, opts); if (res || command != TODO_REWORD) goto leave; - flags |= EDIT_MSG | AMEND_MSG; - if (command == TODO_REWORD) - flags |= VERIFY_MSG; + flags |= EDIT_MSG | AMEND_MSG | VERIFY_MSG; msg_file = NULL; goto fast_forward_edit; } @@ -1253,20 +1261,26 @@ static int parse_insn_line(struct todo_item *item, const char *bol, char *eol) if (i >= TODO_COMMENT) return -1; + /* Eat up extra spaces/ tabs before object name */ + padding = strspn(bol, " \t"); + bol += padding; + if (item->command == TODO_NOOP) { + if (bol != eol) + return error(_("%s does not accept arguments: '%s'"), + command_to_string(item->command), bol); item->commit = NULL; item->arg = bol; item->arg_len = eol - bol; return 0; } - /* Eat up extra spaces/ tabs before object name */ - padding = strspn(bol, " \t"); if (!padding) - return -1; - bol += padding; + return error(_("missing arguments for %s"), + command_to_string(item->command)); if (item->command == TODO_EXEC) { + item->commit = NULL; item->arg = bol; item->arg_len = (int)(eol - bol); return 0; @@ -2442,14 +2456,16 @@ void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) strbuf_release(&sob); } -int sequencer_make_script(int keep_empty, FILE *out, - int argc, const char **argv) +int sequencer_make_script(FILE *out, int argc, const char **argv, + unsigned flags) { char *format = NULL; struct pretty_print_context pp = {0}; struct strbuf buf = STRBUF_INIT; struct rev_info revs; struct commit *commit; + int keep_empty = flags & TODO_LIST_KEEP_EMPTY; + const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; init_revisions(&revs, NULL); revs.verbose_header = 1; @@ -2482,7 +2498,8 @@ int sequencer_make_script(int keep_empty, FILE *out, strbuf_reset(&buf); if (!keep_empty && is_original_commit_empty(commit)) strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, "pick %s ", oid_to_hex(&commit->object.oid)); + strbuf_addf(&buf, "%s %s ", insn, + oid_to_hex(&commit->object.oid)); pretty_print_commit(&pp, commit, &buf); strbuf_addch(&buf, '\n'); fputs(buf.buf, out); @@ -2491,61 +2508,93 @@ int sequencer_make_script(int keep_empty, FILE *out, return 0; } - -int transform_todo_ids(int shorten_ids) +/* + * Add commands after pick and (series of) squash/fixup commands + * in the todo list. + */ +int sequencer_add_exec_commands(const char *commands) { const char *todo_file = rebase_path_todo(); struct todo_list todo_list = TODO_LIST_INIT; - int fd, res, i; - FILE *out; + struct todo_item *item; + struct strbuf *buf = &todo_list.buf; + size_t offset = 0, commands_len = strlen(commands); + int i, first; - strbuf_reset(&todo_list.buf); - fd = open(todo_file, O_RDONLY); - if (fd < 0) - return error_errno(_("could not open '%s'"), todo_file); - if (strbuf_read(&todo_list.buf, fd, 0) < 0) { - close(fd); + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); - } - close(fd); - res = parse_insn_buffer(todo_list.buf.buf, &todo_list); - if (res) { + if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); return error(_("unusable todo list: '%s'"), todo_file); } - out = fopen(todo_file, "w"); - if (!out) { + first = 1; + /* insert <commands> before every pick except the first one */ + for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + if (item->command == TODO_PICK && !first) { + strbuf_insert(buf, item->offset_in_buf + offset, + commands, commands_len); + offset += commands_len; + } + first = 0; + } + + /* append final <commands> */ + strbuf_add(buf, commands, commands_len); + + i = write_message(buf->buf, buf->len, todo_file, 0); + todo_list_release(&todo_list); + return i; +} + +int transform_todos(unsigned flags) +{ + const char *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + struct strbuf buf = STRBUF_INIT; + struct todo_item *item; + int i; + + if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) + return error(_("could not read '%s'."), todo_file); + + if (parse_insn_buffer(todo_list.buf.buf, &todo_list)) { todo_list_release(&todo_list); - return error(_("unable to open '%s' for writing"), todo_file); + return error(_("unusable todo list: '%s'"), todo_file); } - for (i = 0; i < todo_list.nr; i++) { - struct todo_item *item = todo_list.items + i; - int bol = item->offset_in_buf; - const char *p = todo_list.buf.buf + bol; - int eol = i + 1 < todo_list.nr ? - todo_list.items[i + 1].offset_in_buf : - todo_list.buf.len; - - if (item->command >= TODO_EXEC && item->command != TODO_DROP) - fwrite(p, eol - bol, 1, out); - else { - const char *id = shorten_ids ? - short_commit_name(item->commit) : - oid_to_hex(&item->commit->object.oid); - int len; - - p += strspn(p, " \t"); /* left-trim command */ - len = strcspn(p, " \t"); /* length of command */ - - fprintf(out, "%.*s %s %.*s\n", - len, p, id, item->arg_len, item->arg); + + for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { + /* if the item is not a command write it and continue */ + if (item->command >= TODO_COMMENT) { + strbuf_addf(&buf, "%.*s\n", item->arg_len, item->arg); + continue; + } + + /* add command to the buffer */ + if (flags & TODO_LIST_ABBREVIATE_CMDS) + strbuf_addch(&buf, command_to_char(item->command)); + else + strbuf_addstr(&buf, command_to_string(item->command)); + + /* add commit id */ + if (item->commit) { + const char *oid = flags & TODO_LIST_SHORTEN_IDS ? + short_commit_name(item->commit) : + oid_to_hex(&item->commit->object.oid); + + strbuf_addf(&buf, " %s", oid); } + /* add all the rest */ + if (!item->arg_len) + strbuf_addch(&buf, '\n'); + else + strbuf_addf(&buf, " %.*s\n", item->arg_len, item->arg); } - fclose(out); + + i = write_message(buf.buf, buf.len, todo_file, 0); todo_list_release(&todo_list); - return 0; + return i; } enum check_level { diff --git a/sequencer.h b/sequencer.h index 6f3d3df82c..81f6d7d393 100644 --- a/sequencer.h +++ b/sequencer.h @@ -45,10 +45,14 @@ int sequencer_continue(struct replay_opts *opts); int sequencer_rollback(struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); -int sequencer_make_script(int keep_empty, FILE *out, - int argc, const char **argv); +#define TODO_LIST_KEEP_EMPTY (1U << 0) +#define TODO_LIST_SHORTEN_IDS (1U << 1) +#define TODO_LIST_ABBREVIATE_CMDS (1U << 2) +int sequencer_make_script(FILE *out, int argc, const char **argv, + unsigned flags); -int transform_todo_ids(int shorten_ids); +int sequencer_add_exec_commands(const char *command); +int transform_todos(unsigned flags); int check_todo_list(void); int skip_unnecessary_picks(void); int rearrange_squash(void); @@ -434,16 +434,15 @@ static int check_repo_format(const char *var, const char *value, void *vdata) return 0; } -static int check_repository_format_gently(const char *gitdir, int *nongit_ok) +static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok) { struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - struct repository_format candidate; int has_common; has_common = get_common_dir(&sb, gitdir); strbuf_addstr(&sb, "/config"); - read_repository_format(&candidate, sb.buf); + read_repository_format(candidate, sb.buf); strbuf_release(&sb); /* @@ -451,10 +450,10 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) * we treat a missing config as a silent "ok", even when nongit_ok * is unset. */ - if (candidate.version < 0) + if (candidate->version < 0) return 0; - if (verify_repository_format(&candidate, &err) < 0) { + if (verify_repository_format(candidate, &err) < 0) { if (nongit_ok) { warning("%s", err.buf); strbuf_release(&err); @@ -464,21 +463,21 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok) die("%s", err.buf); } - repository_format_precious_objects = candidate.precious_objects; - string_list_clear(&candidate.unknown_extensions, 0); + repository_format_precious_objects = candidate->precious_objects; + string_list_clear(&candidate->unknown_extensions, 0); if (!has_common) { - if (candidate.is_bare != -1) { - is_bare_repository_cfg = candidate.is_bare; + if (candidate->is_bare != -1) { + is_bare_repository_cfg = candidate->is_bare; if (is_bare_repository_cfg == 1) inside_work_tree = -1; } - if (candidate.work_tree) { + if (candidate->work_tree) { free(git_work_tree_cfg); - git_work_tree_cfg = candidate.work_tree; + git_work_tree_cfg = candidate->work_tree; inside_work_tree = -1; } } else { - free(candidate.work_tree); + free(candidate->work_tree); } return 0; @@ -489,6 +488,7 @@ int read_repository_format(struct repository_format *format, const char *path) memset(format, 0, sizeof(*format)); format->version = -1; format->is_bare = -1; + format->hash_algo = GIT_HASH_SHA1; string_list_init(&format->unknown_extensions, 1); git_config_from_file(check_repo_format, path, format); return format->version; @@ -625,6 +625,7 @@ cleanup_return: static const char *setup_explicit_git_dir(const char *gitdirenv, struct strbuf *cwd, + struct repository_format *repo_fmt, int *nongit_ok) { const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); @@ -650,7 +651,7 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, die("Not a git repository: '%s'", gitdirenv); } - if (check_repository_format_gently(gitdirenv, nongit_ok)) { + if (check_repository_format_gently(gitdirenv, repo_fmt, nongit_ok)) { free(gitfile); return NULL; } @@ -723,9 +724,10 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, static const char *setup_discovered_git_dir(const char *gitdir, struct strbuf *cwd, int offset, + struct repository_format *repo_fmt, int *nongit_ok) { - if (check_repository_format_gently(gitdir, nongit_ok)) + if (check_repository_format_gently(gitdir, repo_fmt, nongit_ok)) return NULL; /* --work-tree is set without --git-dir; use discovered one */ @@ -737,7 +739,7 @@ static const char *setup_discovered_git_dir(const char *gitdir, gitdir = to_free = real_pathdup(gitdir, 1); if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); - ret = setup_explicit_git_dir(gitdir, cwd, nongit_ok); + ret = setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok); free(to_free); return ret; } @@ -769,11 +771,12 @@ static const char *setup_discovered_git_dir(const char *gitdir, /* #16.1, #17.1, #20.1, #21.1, #22.1 (see t1510) */ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, + struct repository_format *repo_fmt, int *nongit_ok) { int root_len; - if (check_repository_format_gently(".", nongit_ok)) + if (check_repository_format_gently(".", repo_fmt, nongit_ok)) return NULL; setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); @@ -785,7 +788,7 @@ static const char *setup_bare_git_dir(struct strbuf *cwd, int offset, gitdir = offset == cwd->len ? "." : xmemdupz(cwd->buf, offset); if (chdir(cwd->buf)) die_errno("Could not come back to cwd"); - return setup_explicit_git_dir(gitdir, cwd, nongit_ok); + return setup_explicit_git_dir(gitdir, cwd, repo_fmt, nongit_ok); } inside_git_dir = 1; @@ -923,7 +926,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, * - ../.git * - ../.git/ * - ../ (bare) - * - ../../.git/ + * - ../../.git * etc. */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); @@ -1026,6 +1029,7 @@ const char *setup_git_directory_gently(int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT; const char *prefix; + struct repository_format repo_fmt; /* * We may have read an incomplete configuration before @@ -1053,18 +1057,18 @@ const char *setup_git_directory_gently(int *nongit_ok) prefix = NULL; break; case GIT_DIR_EXPLICIT: - prefix = setup_explicit_git_dir(gitdir.buf, &cwd, nongit_ok); + prefix = setup_explicit_git_dir(gitdir.buf, &cwd, &repo_fmt, nongit_ok); break; case GIT_DIR_DISCOVERED: if (dir.len < cwd.len && chdir(dir.buf)) die(_("Cannot change to '%s'"), dir.buf); prefix = setup_discovered_git_dir(gitdir.buf, &cwd, dir.len, - nongit_ok); + &repo_fmt, nongit_ok); break; case GIT_DIR_BARE: if (dir.len < cwd.len && chdir(dir.buf)) die(_("Cannot change to '%s'"), dir.buf); - prefix = setup_bare_git_dir(&cwd, dir.len, nongit_ok); + prefix = setup_bare_git_dir(&cwd, dir.len, &repo_fmt, nongit_ok); break; case GIT_DIR_HIT_CEILING: prefix = setup_nongit(cwd.buf, nongit_ok); @@ -1110,6 +1114,8 @@ const char *setup_git_directory_gently(int *nongit_ok) repo_set_gitdir(the_repository, gitdir); setup_git_env(); } + if (startup_info->have_repository) + repo_set_hash_algo(the_repository, repo_fmt.hash_algo); } strbuf_release(&dir); @@ -1171,7 +1177,8 @@ int git_config_perm(const char *var, const char *value) void check_repository_format(void) { - check_repository_format_gently(get_git_dir(), NULL); + struct repository_format repo_fmt; + check_repository_format_gently(get_git_dir(), &repo_fmt, NULL); startup_info->have_repository = 1; } diff --git a/sha1_file.c b/sha1_file.c index 8a7c6b7eba..3da70ac650 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -39,6 +39,64 @@ const struct object_id empty_blob_oid = { EMPTY_BLOB_SHA1_BIN_LITERAL }; +static void git_hash_sha1_init(void *ctx) +{ + git_SHA1_Init((git_SHA_CTX *)ctx); +} + +static void git_hash_sha1_update(void *ctx, const void *data, size_t len) +{ + git_SHA1_Update((git_SHA_CTX *)ctx, data, len); +} + +static void git_hash_sha1_final(unsigned char *hash, void *ctx) +{ + git_SHA1_Final(hash, (git_SHA_CTX *)ctx); +} + +static void git_hash_unknown_init(void *ctx) +{ + die("trying to init unknown hash"); +} + +static void git_hash_unknown_update(void *ctx, const void *data, size_t len) +{ + die("trying to update unknown hash"); +} + +static void git_hash_unknown_final(unsigned char *hash, void *ctx) +{ + die("trying to finalize unknown hash"); +} + +const struct git_hash_algo hash_algos[GIT_HASH_NALGOS] = { + { + NULL, + 0x00000000, + 0, + 0, + 0, + git_hash_unknown_init, + git_hash_unknown_update, + git_hash_unknown_final, + NULL, + NULL, + }, + { + "sha-1", + /* "sha1", big-endian */ + 0x73686131, + sizeof(git_SHA_CTX), + GIT_SHA1_RAWSZ, + GIT_SHA1_HEXSZ, + git_hash_sha1_init, + git_hash_sha1_update, + git_hash_sha1_final, + &empty_tree_oid, + &empty_blob_oid, + }, +}; + /* * This is meant to hold a *small* number of objects that you would * want read_sha1_file() to be able to return, but yet you do not want @@ -74,6 +132,18 @@ static struct cached_object *find_cached_object(const unsigned char *sha1) return NULL; } + +static enum safe_crlf get_safe_crlf(unsigned flags) +{ + if (flags & HASH_RENORMALIZE) + return SAFE_CRLF_RENORMALIZE; + else if (flags & HASH_WRITE_OBJECT) + return safe_crlf; + else + return SAFE_CRLF_FALSE; +} + + int mkdir_in_gitdir(const char *path) { if (mkdir(path, 0777)) { @@ -1152,6 +1222,9 @@ int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi, lookup_replace_object(sha1) : sha1; + if (is_null_sha1(real)) + return -1; + if (!oi) oi = &blank_oi; @@ -1679,7 +1752,7 @@ static int index_mem(struct object_id *oid, void *buf, size_t size, if ((type == OBJ_BLOB) && path) { struct strbuf nbuf = STRBUF_INIT; if (convert_to_git(&the_index, path, buf, size, &nbuf, - write_object ? safe_crlf : SAFE_CRLF_FALSE)) { + get_safe_crlf(flags))) { buf = strbuf_detach(&nbuf, &size); re_allocated = 1; } @@ -1713,7 +1786,7 @@ static int index_stream_convert_blob(struct object_id *oid, int fd, assert(would_convert_to_git_filter_fd(path)); convert_to_git_filter_fd(&the_index, path, fd, &sbuf, - write_object ? safe_crlf : SAFE_CRLF_FALSE); + get_safe_crlf(flags)); if (write_object) ret = write_sha1_file(sbuf.buf, sbuf.len, typename(OBJ_BLOB), @@ -1891,7 +1964,6 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr, origlen = path->len; strbuf_complete(path, '/'); strbuf_addf(path, "%02x", subdir_nr); - baselen = path->len; dir = opendir(path->buf); if (!dir) { @@ -1902,15 +1974,18 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr, } oid.hash[0] = subdir_nr; + strbuf_addch(path, '/'); + baselen = path->len; while ((de = readdir(dir))) { + size_t namelen; if (is_dot_or_dotdot(de->d_name)) continue; + namelen = strlen(de->d_name); strbuf_setlen(path, baselen); - strbuf_addf(path, "/%s", de->d_name); - - if (strlen(de->d_name) == GIT_SHA1_HEXSZ - 2 && + strbuf_add(path, de->d_name, namelen); + if (namelen == GIT_SHA1_HEXSZ - 2 && !hex_to_bytes(oid.hash + 1, de->d_name, GIT_SHA1_RAWSZ - 1)) { if (obj_cb) { @@ -1929,7 +2004,7 @@ int for_each_file_in_obj_subdir(unsigned int subdir_nr, } closedir(dir); - strbuf_setlen(path, baselen); + strbuf_setlen(path, baselen - 1); if (!r && subdir_cb) r = subdir_cb(subdir_nr, path->buf, data); diff --git a/sha1_name.c b/sha1_name.c index 9a2d5caf3b..611c7d24dd 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1438,9 +1438,19 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name) strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL); else strbuf_addstr(sb, name); - if (name[0] == '-') - return -1; + + /* + * This splice must be done even if we end up rejecting the + * name; builtin/branch.c::copy_or_rename_branch() still wants + * to see what the name expanded to so that "branch -m" can be + * used as a tool to correct earlier mistakes. + */ strbuf_splice(sb, 0, 0, "refs/heads/", 11); + + if (*name == '-' || + !strcmp(sb->buf, "refs/heads/HEAD")) + return -1; + return check_refname_format(sb->buf, 0); } diff --git a/sideband.c b/sideband.c index 1e4d684d6c..6d7f943e43 100644 --- a/sideband.c +++ b/sideband.c @@ -20,13 +20,12 @@ int recv_sideband(const char *me, int in_stream, int out) { - const char *term, *suffix; + const char *suffix; char buf[LARGE_PACKET_MAX + 1]; struct strbuf outbuf = STRBUF_INIT; int retval = 0; - term = getenv("TERM"); - if (isatty(2) && term && strcmp(term, "dumb")) + if (isatty(2) && !is_terminal_dumb()) suffix = ANSI_SUFFIX; else suffix = DUMB_SUFFIX; @@ -11,6 +11,28 @@ int starts_with(const char *str, const char *prefix) return 0; } +int skip_to_optional_arg_default(const char *str, const char *prefix, + const char **arg, const char *def) +{ + const char *p; + + if (!skip_prefix(str, prefix, &p)) + return 0; + + if (!*p) { + if (arg) + *arg = def; + return 1; + } + + if (*p != '=') + return 0; + + if (arg) + *arg = p + 1; + return 1; +} + /* * Used as the default ->buf value, so that people can always assume * buf is non NULL and ->buf is NUL terminated even for a freshly @@ -386,12 +408,15 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint) { + size_t oldalloc = sb->alloc; ssize_t cnt; strbuf_grow(sb, hint ? hint : 8192); cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1); if (cnt > 0) strbuf_setlen(sb, sb->len + cnt); + else if (oldalloc == 0) + strbuf_release(sb); return cnt; } @@ -658,7 +683,7 @@ static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len, (!reserved && is_rfc3986_reserved(ch))) strbuf_addch(sb, ch); else - strbuf_addf(sb, "%%%02x", ch); + strbuf_addf(sb, "%%%02x", (unsigned char)ch); } } @@ -480,15 +480,6 @@ extern int strbuf_normalize_path(struct strbuf *sb); */ extern void strbuf_stripspace(struct strbuf *buf, int skip_comments); -/** - * Temporary alias until all topic branches have switched to use - * strbuf_stripspace directly. - */ -static inline void stripspace(struct strbuf *buf, int skip_comments) -{ - strbuf_stripspace(buf, skip_comments); -} - static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) { if (strip_suffix_mem(sb->buf, &sb->len, suffix)) { diff --git a/submodule.c b/submodule.c index bb531e0e59..2967704317 100644 --- a/submodule.c +++ b/submodule.c @@ -1,3 +1,5 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS + #include "cache.h" #include "repository.h" #include "config.h" @@ -55,14 +57,15 @@ int is_gitmodules_unmerged(const struct index_state *istate) * future version when we learn to stage the changes we do ourselves without * staging any previous modifications. */ -int is_staging_gitmodules_ok(const struct index_state *istate) +int is_staging_gitmodules_ok(struct index_state *istate) { int pos = index_name_pos(istate, GITMODULES_FILE, strlen(GITMODULES_FILE)); if ((pos >= 0) && (pos < istate->cache_nr)) { struct stat st; if (lstat(GITMODULES_FILE, &st) == 0 && - ce_match_stat(istate->cache[pos], &st, CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED) + ie_match_stat(istate, istate->cache[pos], &st, + CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED) return 0; } @@ -143,9 +146,9 @@ int remove_path_from_gitmodules(const char *path) return 0; } -void stage_updated_gitmodules(void) +void stage_updated_gitmodules(struct index_state *istate) { - if (add_file_to_cache(GITMODULES_FILE, 0)) + if (add_file_to_index(istate, GITMODULES_FILE, 0)) die(_("staging updated .gitmodules failed")); } @@ -587,7 +590,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path, struct object_id *one, struct object_id *two, unsigned dirty_submodule) { - const struct object_id *old = &empty_tree_oid, *new = &empty_tree_oid; + const struct object_id *old = the_hash_algo->empty_tree, *new = the_hash_algo->empty_tree; struct commit *left = NULL, *right = NULL; struct commit_list *merge_bases = NULL; struct child_process cp = CHILD_PROCESS_INIT; @@ -1178,7 +1181,7 @@ int submodule_touches_in_range(struct object_id *excl_oid, struct submodule_parallel_fetch { int count; struct argv_array args; - const char *work_tree; + struct repository *r; const char *prefix; int command_line_option; int default_option; @@ -1199,7 +1202,7 @@ static int get_fetch_recurse_config(const struct submodule *submodule, int fetch_recurse = submodule->fetch_recurse; key = xstrfmt("submodule.%s.fetchRecurseSubmodules", submodule->name); - if (!repo_config_get_string_const(the_repository, key, &value)) { + if (!repo_config_get_string_const(spf->r, key, &value)) { fetch_recurse = parse_fetch_recurse_submodules_arg(key, value); } free(key); @@ -1218,11 +1221,11 @@ static int get_next_submodule(struct child_process *cp, int ret = 0; struct submodule_parallel_fetch *spf = data; - for (; spf->count < active_nr; spf->count++) { + for (; spf->count < spf->r->index->cache_nr; spf->count++) { struct strbuf submodule_path = STRBUF_INIT; struct strbuf submodule_git_dir = STRBUF_INIT; struct strbuf submodule_prefix = STRBUF_INIT; - const struct cache_entry *ce = active_cache[spf->count]; + const struct cache_entry *ce = spf->r->index->cache[spf->count]; const char *git_dir, *default_argv; const struct submodule *submodule; struct submodule default_submodule = SUBMODULE_INIT; @@ -1230,7 +1233,7 @@ static int get_next_submodule(struct child_process *cp, if (!S_ISGITLINK(ce->ce_mode)) continue; - submodule = submodule_from_path(&null_oid, ce->name); + submodule = submodule_from_cache(spf->r, &null_oid, ce->name); if (!submodule) { const char *name = default_name_or_path(ce->name); if (name) { @@ -1256,7 +1259,7 @@ static int get_next_submodule(struct child_process *cp, continue; } - strbuf_addf(&submodule_path, "%s/%s", spf->work_tree, ce->name); + strbuf_repo_worktree_path(&submodule_path, spf->r, "%s", ce->name); strbuf_addf(&submodule_git_dir, "%s/.git", submodule_path.buf); strbuf_addf(&submodule_prefix, "%s%s/", spf->prefix, ce->name); git_dir = read_gitfile(submodule_git_dir.buf); @@ -1309,7 +1312,8 @@ static int fetch_finish(int retvalue, struct strbuf *err, return 0; } -int fetch_populated_submodules(const struct argv_array *options, +int fetch_populated_submodules(struct repository *r, + const struct argv_array *options, const char *prefix, int command_line_option, int default_option, int quiet, int max_parallel_jobs) @@ -1317,16 +1321,16 @@ int fetch_populated_submodules(const struct argv_array *options, int i; struct submodule_parallel_fetch spf = SPF_INIT; - spf.work_tree = get_git_work_tree(); + spf.r = r; spf.command_line_option = command_line_option; spf.default_option = default_option; spf.quiet = quiet; spf.prefix = prefix; - if (!spf.work_tree) + if (!r->worktree) goto out; - if (read_cache() < 0) + if (repo_read_index(r) < 0) die("index file corrupt"); argv_array_push(&spf.args, "fetch"); @@ -1670,7 +1674,8 @@ int submodule_move_head(const char *path, cp.dir = path; prepare_submodule_repo_env(&cp.env_array); - argv_array_pushl(&cp.args, "update-ref", "HEAD", new, NULL); + argv_array_pushl(&cp.args, "update-ref", "HEAD", + "--no-deref", new, NULL); if (run_command(&cp)) { ret = -1; diff --git a/submodule.h b/submodule.h index f0da0277a4..b9b7ef0030 100644 --- a/submodule.h +++ b/submodule.h @@ -34,10 +34,10 @@ struct submodule_update_strategy { #define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} extern int is_gitmodules_unmerged(const struct index_state *istate); -extern int is_staging_gitmodules_ok(const struct index_state *istate); +extern int is_staging_gitmodules_ok(struct index_state *istate); extern int update_path_in_gitmodules(const char *oldpath, const char *newpath); extern int remove_path_from_gitmodules(const char *path); -extern void stage_updated_gitmodules(void); +extern void stage_updated_gitmodules(struct index_state *istate); extern void set_diffopt_flags_from_submodule_config(struct diff_options *, const char *path); extern int git_default_submodule_config(const char *var, const char *value, void *cb); @@ -76,10 +76,12 @@ extern int should_update_submodules(void); */ extern const struct submodule *submodule_from_ce(const struct cache_entry *ce); extern void check_for_new_submodule_commits(struct object_id *oid); -extern int fetch_populated_submodules(const struct argv_array *options, - const char *prefix, int command_line_option, - int default_option, - int quiet, int max_parallel_jobs); +extern int fetch_populated_submodules(struct repository *r, + const struct argv_array *options, + const char *prefix, + int command_line_option, + int default_option, + int quiet, int max_parallel_jobs); extern unsigned is_submodule_modified(const char *path, int ignore_untracked); extern int submodule_uses_gitfile(const char *path); diff --git a/t/Makefile b/t/Makefile index 1bb06c36f2..96317a35f4 100644 --- a/t/Makefile +++ b/t/Makefile @@ -8,6 +8,7 @@ #GIT_TEST_OPTS = --verbose --debug SHELL_PATH ?= $(SHELL) +TEST_SHELL_PATH ?= $(SHELL_PATH) PERL_PATH ?= /usr/bin/perl TAR ?= $(TAR) RM ?= rm -f @@ -23,6 +24,7 @@ endif # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) @@ -42,11 +44,11 @@ failed: test -z "$$failed" || $(MAKE) $$failed prove: pre-clean $(TEST_LINT) - @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) + @echo "*** prove ***"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) $(MAKE) clean-except-prove-cache $(T): - @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + @echo "*** $@ ***"; '$(TEST_SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' @@ -332,13 +332,10 @@ Writing Tests ------------- The test script is written as a shell script. It should start -with the standard "#!/bin/sh" with copyright notices, and an +with the standard "#!/bin/sh", and an assignment to variable 'test_description', like this: #!/bin/sh - # - # Copyright (c) 2005 Junio C Hamano - # test_description='xxx test (option --frotz) @@ -677,6 +674,11 @@ library for your script to use. <expected> file. This behaves like "cmp" but produces more helpful output when the test is run with "-v" option. + - test_cmp_rev <expected> <actual> + + Check whether the <expected> rev points to the same commit as the + <actual> rev. + - test_line_count (= | -lt | -ge | ...) <length> <file> Check whether a file has the length it is expected to. @@ -808,6 +810,18 @@ use these, and "test_set_prereq" for how to define your own. Git was compiled with support for PCRE. Wrap any tests that use git-grep --perl-regexp or git-grep -P in these. + - LIBPCRE1 + + Git was compiled with PCRE v1 support via + USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some + reason need v1 of the PCRE library instead of v2 in these. + + - LIBPCRE2 + + Git was compiled with PCRE v2 support via + USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some + reason need v2 of the PCRE library instead of v1 in these. + - CASE_INSENSITIVE_FS Test is run on a case insensitive file system. diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index 03dc9d2852..e07f028437 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -21,6 +21,7 @@ while (<>) { /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; /\btest\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; + /\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (please use test_line_count)'; /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; # this resets our $. for each file close ARGV if eof; diff --git a/t/helper/.gitignore b/t/helper/.gitignore index d02f9b39ac..2bad28af92 100644 --- a/t/helper/.gitignore +++ b/t/helper/.gitignore @@ -1,40 +1,5 @@ -/test-chmtime -/test-ctype -/test-config -/test-date -/test-delta -/test-drop-caches -/test-dump-cache-tree -/test-dump-fsmonitor -/test-dump-split-index -/test-dump-untracked-cache -/test-fake-ssh -/test-scrap-cache-tree -/test-genrandom -/test-hashmap -/test-index-version -/test-lazy-init-name-hash -/test-line-buffer -/test-match-trees -/test-mergesort -/test-mktemp -/test-online-cpus -/test-parse-options -/test-path-utils -/test-prio-queue -/test-read-cache -/test-ref-store -/test-regex -/test-revision-walking -/test-run-command -/test-sha1 -/test-sha1-array -/test-sigchain -/test-strcmp-offset -/test-string-list -/test-submodule-config -/test-subprocess -/test-svn-fe -/test-urlmatch-normalization -/test-wildmatch -/test-write-cache +* +!*.sh +!*.[ch] +!*.gitignore + diff --git a/t/helper/test-date.c b/t/helper/test-date.c index f414a3ac67..ac83687970 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -5,6 +5,7 @@ static const char *usage_msg = "\n" " test-date show:<format> [time_t]...\n" " test-date parse [date]...\n" " test-date approxidate [date]...\n" +" test-date timestamp [date]...\n" " test-date is64bit\n" " test-date time_t-is64bit\n"; @@ -71,6 +72,15 @@ static void parse_approxidate(const char **argv, struct timeval *now) } } +static void parse_approx_timestamp(const char **argv, struct timeval *now) +{ + for (; *argv; argv++) { + timestamp_t t; + t = approxidate_relative(*argv, now); + printf("%s -> %"PRItime"\n", *argv, t); + } +} + int cmd_main(int argc, const char **argv) { struct timeval now; @@ -95,6 +105,8 @@ int cmd_main(int argc, const char **argv) parse_dates(argv+1, &now); else if (!strcmp(*argv, "approxidate")) parse_approxidate(argv+1, &now); + else if (!strcmp(*argv, "timestamp")) + parse_approx_timestamp(argv+1, &now); else if (!strcmp(*argv, "is64bit")) return sizeof(timestamp_t) == 8 ? 0 : 1; else if (!strcmp(*argv, "time_t-is64bit")) diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c new file mode 100644 index 0000000000..90dc97a9d0 --- /dev/null +++ b/t/helper/test-example-decorate.c @@ -0,0 +1,74 @@ +#include "cache.h" +#include "object.h" +#include "decorate.h" + +int cmd_main(int argc, const char **argv) +{ + struct decoration n; + struct object_id one_oid = { {1} }; + struct object_id two_oid = { {2} }; + struct object_id three_oid = { {3} }; + struct object *one, *two, *three; + + int decoration_a, decoration_b; + + void *ret; + + int i, objects_noticed = 0; + + /* + * The struct must be zero-initialized. + */ + memset(&n, 0, sizeof(n)); + + /* + * Add 2 objects, one with a non-NULL decoration and one with a NULL + * decoration. + */ + one = lookup_unknown_object(one_oid.hash); + two = lookup_unknown_object(two_oid.hash); + ret = add_decoration(&n, one, &decoration_a); + if (ret) + die("BUG: when adding a brand-new object, NULL should be returned"); + ret = add_decoration(&n, two, NULL); + if (ret) + die("BUG: when adding a brand-new object, NULL should be returned"); + + /* + * When re-adding an already existing object, the old decoration is + * returned. + */ + ret = add_decoration(&n, one, NULL); + if (ret != &decoration_a) + die("BUG: when readding an already existing object, existing decoration should be returned"); + ret = add_decoration(&n, two, &decoration_b); + if (ret) + die("BUG: when readding an already existing object, existing decoration should be returned"); + + /* + * Lookup returns the added declarations, or NULL if the object was + * never added. + */ + ret = lookup_decoration(&n, one); + if (ret) + die("BUG: lookup should return added declaration"); + ret = lookup_decoration(&n, two); + if (ret != &decoration_b) + die("BUG: lookup should return added declaration"); + three = lookup_unknown_object(three_oid.hash); + ret = lookup_decoration(&n, three); + if (ret) + die("BUG: lookup for unknown object should return NULL"); + + /* + * The user can also loop through all entries. + */ + for (i = 0; i < n.size; i++) { + if (n.entries[i].base) + objects_noticed++; + } + if (objects_noticed != 2) + die("BUG: should have 2 objects"); + + return 0; +} diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c index 6368a89345..297fb01d61 100644 --- a/t/helper/test-lazy-init-name-hash.c +++ b/t/helper/test-lazy-init-name-hash.c @@ -112,7 +112,7 @@ static void analyze_run(void) { uint64_t t1s, t1m, t2s, t2m; int cache_nr_limit; - int nr_threads_used; + int nr_threads_used = 0; int i; int nr; diff --git a/t/interop/i5700-protocol-transition.sh b/t/interop/i5700-protocol-transition.sh new file mode 100755 index 0000000000..97e8e580ef --- /dev/null +++ b/t/interop/i5700-protocol-transition.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +VERSION_A=. +VERSION_B=v2.0.0 + +: ${LIB_GIT_DAEMON_PORT:=5700} +LIB_GIT_DAEMON_COMMAND='git.b daemon' + +test_description='clone and fetch by client who is trying to use a new protocol' +. ./interop-lib.sh +. "$TEST_DIRECTORY"/lib-git-daemon.sh + +start_git_daemon --export-all + +repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo + +test_expect_success "create repo served by $VERSION_B" ' + git.b init "$repo" && + git.b -C "$repo" commit --allow-empty -m one +' + +test_expect_success "git:// clone with $VERSION_A and protocol v1" ' + GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log && + git.a -C child log -1 --format=%s >actual && + git.b -C "$repo" log -1 --format=%s >expect && + test_cmp expect actual && + grep "version=1" log +' + +test_expect_success "git:// fetch with $VERSION_A and protocol v1" ' + git.b -C "$repo" commit --allow-empty -m two && + git.b -C "$repo" log -1 --format=%s >expect && + + GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log && + git.a -C child log -1 --format=%s FETCH_HEAD >actual && + + test_cmp expect actual && + grep "version=1" log && + ! grep "version 1" log +' + +stop_git_daemon + +test_expect_success "create repo served by $VERSION_B" ' + git.b init parent && + git.b -C parent commit --allow-empty -m one +' + +test_expect_success "file:// clone with $VERSION_A and protocol v1" ' + GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log && + git.a -C child2 log -1 --format=%s >actual && + git.b -C parent log -1 --format=%s >expect && + test_cmp expect actual && + ! grep "version 1" log +' + +test_expect_success "file:// fetch with $VERSION_A and protocol v1" ' + git.b -C parent commit --allow-empty -m two && + git.b -C parent log -1 --format=%s >expect && + + GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log && + git.a -C child2 log -1 --format=%s FETCH_HEAD >actual && + + test_cmp expect actual && + ! grep "version 1" log +' + +test_done diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 688313ed5c..4c1f81f167 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -17,8 +17,8 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree svn >/dev/null 2>&1 if test $? -ne 1 then - skip_all='skipping git svn tests, svn not found' - test_done + skip_all='skipping git svn tests, svn not found' + test_done fi svnrepo=$PWD/svnrepo @@ -110,18 +110,20 @@ EOF } require_svnserve () { - if test -z "$SVNSERVE_PORT" - then - skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)' - test_done - fi + test_tristate GIT_TEST_SVNSERVE + if ! test "$GIT_TEST_SVNSERVE" = true + then + skip_all='skipping svnserve test. (set $GIT_TEST_SVNSERVE to enable)' + test_done + fi } start_svnserve () { - svnserve --listen-port $SVNSERVE_PORT \ - --root "$rawsvnrepo" \ - --listen-once \ - --listen-host 127.0.0.1 & + SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} + svnserve --listen-port $SVNSERVE_PORT \ + --root "$rawsvnrepo" \ + --listen-once \ + --listen-host 127.0.0.1 & } prepare_a_utf8_locale () { diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 43679a4c64..a5d3b2cbaa 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -31,7 +31,7 @@ then chmod 0700 ./gpghome && GNUPGHOME="$(pwd)/gpghome" && export GNUPGHOME && - (gpgconf --kill gpg-agent 2>&1 >/dev/null || : ) && + (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \ "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \ diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 0642ae7e6e..724d9ae462 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -25,6 +25,9 @@ ErrorLog error.log <IfModule !mod_headers.c> LoadModule headers_module modules/mod_headers.so </IfModule> +<IfModule !mod_setenvif.c> + LoadModule setenvif_module modules/mod_setenvif.so +</IfModule> <IfVersion < 2.4> LockFile accept.lock @@ -76,6 +79,8 @@ PassEnv ASAN_OPTIONS PassEnv GIT_TRACE PassEnv GIT_CONFIG_NOSYSTEM +SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0 + Alias /dumb/ www/ Alias /auth/dumb/ www/auth/dumb/ diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index bb94c23209..38dadd2c29 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -554,6 +554,10 @@ test_submodule_switch_common() { # - if succeeds, once "git submodule update" is invoked, the contents of # submodule directories are updated # +# If the command under test is known to not work with submodules in certain +# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests +# below to 1. +# # Use as follows: # # my_func () { @@ -622,19 +626,11 @@ test_submodule_forced_switch () { # - Removing a submodule with a git directory absorbs the submodules # git directory first into the superproject. -test_submodule_switch_recursing_with_args () { - cmd_args="$1" - command="git $cmd_args --recurse-submodules" - RESULTDS=success - if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 - then - RESULTDS=failure - fi - RESULTOI=success - if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 - then - RESULTOI=failure - fi +# Internal function; use test_submodule_switch_recursing_with_args() or +# test_submodule_forced_switch_recursing_with_args() instead. +test_submodule_recursing_with_args_common() { + command="$1" + ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear checks it out ... test_expect_success "$command: added submodule is checked out" ' @@ -648,7 +644,7 @@ test_submodule_switch_recursing_with_args () { test_submodule_content sub1 origin/add_sub1 ) ' - # ... ignoring an empty existing directory ... + # ... ignoring an empty existing directory. test_expect_success "$command: added submodule is checked out in empty dir" ' prolog && reset_work_tree_to_interested no_submodule && @@ -661,34 +657,23 @@ test_submodule_switch_recursing_with_args () { test_submodule_content sub1 origin/add_sub1 ) ' - # ... unless there is an untracked file in its place. - test_expect_success "$command: added submodule doesn't remove untracked file with same name" ' - prolog && - reset_work_tree_to_interested no_submodule && - ( - cd submodule_update && - git branch -t add_sub1 origin/add_sub1 && - : >sub1 && - test_must_fail $command add_sub1 && - test_superproject_content origin/no_submodule && - test_must_be_empty sub1 - ) - ' - # ... but an ignored file is fine. - test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" ' - test_when_finished "rm submodule_update/.git/info/exclude" && + test_expect_success "$command: submodule branch is not changed, detach HEAD instead" ' prolog && - reset_work_tree_to_interested no_submodule && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t add_sub1 origin/add_sub1 && - : >sub1 && - echo sub1 >.git/info/exclude - $command add_sub1 && - test_superproject_content origin/add_sub1 && - test_submodule_content sub1 origin/add_sub1 + git -C sub1 checkout -b keep_branch && + git -C sub1 rev-parse HEAD >expect && + git branch -t check-keep origin/modify_sub1 && + $command check-keep && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 && + git -C sub1 rev-parse keep_branch >actual && + test_cmp expect actual && + test_must_fail git -C sub1 symbolic-ref HEAD ) ' + # Replacing a tracked file with a submodule produces a checked out submodule test_expect_success "$command: replace tracked file with submodule checks out submodule" ' prolog && @@ -742,33 +727,6 @@ test_submodule_switch_recursing_with_args () { test_git_directory_exists sub1 ) ' - # Replacing a submodule with files in a directory must succeeds - # when the submodule is clean - test_expect_$RESULTDS "$command: replace submodule with a directory" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && - $command replace_sub1_with_directory && - test_superproject_content origin/replace_sub1_with_directory && - test_submodule_content sub1 origin/replace_sub1_with_directory - ) - ' - # ... absorbing a .git directory. - test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && - replace_gitfile_with_git_dir sub1 && - rm -rf .git/modules && - $command replace_sub1_with_directory && - test_superproject_content origin/replace_sub1_with_directory && - test_git_directory_exists sub1 - ) - ' # Replacing it with a file ... test_expect_success "$command: replace submodule with a file" ' @@ -782,7 +740,11 @@ test_submodule_switch_recursing_with_args () { test -f sub1 ) ' - + RESULTDS=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULTDS=failure + fi # ... must check its local work tree for untracked files test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" ' prolog && @@ -794,21 +756,7 @@ test_submodule_switch_recursing_with_args () { test_must_fail $command replace_sub1_with_file && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 - ) - ' - - # ... and ignored files are ignored - test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" ' - test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" && - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t replace_sub1_with_file origin/replace_sub1_with_file && - : >sub1/ignored && - $command replace_sub1_with_file && - test_superproject_content origin/replace_sub1_with_file && - test -f sub1 + test -f sub1/untracked_file ) ' @@ -825,19 +773,6 @@ test_submodule_switch_recursing_with_args () { test_submodule_content sub1 origin/modify_sub1 ) ' - - test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t modify_sub1 origin/modify_sub1 && - git -c submodule.recurse=true $cmd_args modify_sub1 && - test_superproject_content origin/modify_sub1 && - test_submodule_content sub1 origin/modify_sub1 - ) - ' - # Updating a submodule to an invalid sha1 doesn't update the # superproject nor the submodule's work tree. test_expect_success "$command: updating to a missing submodule commit fails" ' @@ -851,126 +786,166 @@ test_submodule_switch_recursing_with_args () { test_submodule_content sub1 origin/add_sub1 ) ' - - # recursing deeper than one level doesn't work yet. - test_expect_success "$command: modified submodule updates submodule recursively" ' - prolog && - reset_work_tree_to_interested add_nested_sub && - ( - cd submodule_update && - git branch -t modify_sub1_recursively origin/modify_sub1_recursively && - $command modify_sub1_recursively && - test_superproject_content origin/modify_sub1_recursively && - test_submodule_content sub1 origin/modify_sub1_recursively && - test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively - ) - ' } -# Test that submodule contents are updated when switching between commits -# that change a submodule, but throwing away local changes in -# the superproject as well as the submodule is allowed. -test_submodule_forced_switch_recursing_with_args () { +# Declares and invokes several tests that, in various situations, checks that +# the provided Git command, when invoked with --recurse-submodules: +# - succeeds in updating the worktree and index of a superproject to a target +# commit, or fails atomically (depending on the test situation) +# - if succeeds, the contents of submodule directories are updated +# +# Specify the Git command so that "git $GIT_COMMAND --recurse-submodules" +# works. +# +# If the command under test is known to not work with submodules in certain +# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests +# below to 1. +# +# Use as follows: +# +# test_submodule_switch_recursing_with_args "$GIT_COMMAND" +test_submodule_switch_recursing_with_args () { cmd_args="$1" command="git $cmd_args --recurse-submodules" - RESULT=success + test_submodule_recursing_with_args_common "$command" + + RESULTDS=success if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 then - RESULT=failure + RESULTDS=failure fi - ######################### Appearing submodule ######################### - # Switching to a commit letting a submodule appear creates empty dir ... - test_expect_success "$command: added submodule is checked out" ' + RESULTOI=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULTOI=failure + fi + # Switching to a commit letting a submodule appear cannot override an + # untracked file. + test_expect_success "$command: added submodule doesn't remove untracked file with same name" ' prolog && reset_work_tree_to_interested no_submodule && ( cd submodule_update && git branch -t add_sub1 origin/add_sub1 && - $command add_sub1 && - test_superproject_content origin/add_sub1 && - test_submodule_content sub1 origin/add_sub1 + : >sub1 && + test_must_fail $command add_sub1 && + test_superproject_content origin/no_submodule && + test_must_be_empty sub1 ) ' - # ... and doesn't care if it already exists ... - test_expect_success "$command: added submodule ignores empty directory" ' + # ... but an ignored file is fine. + test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" ' + test_when_finished "rm submodule_update/.git/info/exclude" && prolog && reset_work_tree_to_interested no_submodule && ( cd submodule_update && git branch -t add_sub1 origin/add_sub1 && - mkdir sub1 && + : >sub1 && + echo sub1 >.git/info/exclude $command add_sub1 && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 ) ' - # ... not caring about an untracked file either - test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + + # Replacing a submodule with files in a directory must succeeds + # when the submodule is clean + test_expect_$RESULTDS "$command: replace submodule with a directory" ' prolog && - reset_work_tree_to_interested no_submodule && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t add_sub1 origin/add_sub1 && - >sub1 && - $command add_sub1 && - test_superproject_content origin/add_sub1 && - test_submodule_content sub1 origin/add_sub1 + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_submodule_content sub1 origin/replace_sub1_with_directory ) ' - # Replacing a tracked file with a submodule checks out the submodule - test_expect_success "$command: replace tracked file with submodule populates the submodule" ' + # ... absorbing a .git directory. + test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' prolog && - reset_work_tree_to_interested replace_sub1_with_file && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && - $command replace_file_with_sub1 && - test_superproject_content origin/replace_file_with_sub1 && - test_submodule_content sub1 origin/replace_file_with_sub1 + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_git_directory_exists sub1 ) ' - # ... as does removing a directory with tracked files with a - # submodule. - test_expect_success "$command: replace directory with submodule" ' + + # ... and ignored files are ignored + test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" ' + test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" && prolog && - reset_work_tree_to_interested replace_sub1_with_directory && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && - $command replace_directory_with_sub1 && - test_superproject_content origin/replace_directory_with_sub1 && - test_submodule_content sub1 origin/replace_directory_with_sub1 + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/ignored && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file && + test -f sub1 ) ' - ######################## Disappearing submodule ####################### - # Removing a submodule doesn't remove its work tree ... - test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" ' + test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" ' prolog && reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t remove_sub1 origin/remove_sub1 && - $command remove_sub1 && - test_superproject_content origin/remove_sub1 && - ! test -e sub1 + git branch -t modify_sub1 origin/modify_sub1 && + git -c submodule.recurse=true $cmd_args modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 ) ' - # ... especially when it contains a .git directory. - test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" ' + + # recursing deeper than one level doesn't work yet. + test_expect_success "$command: modified submodule updates submodule recursively" ' prolog && - reset_work_tree_to_interested add_sub1 && + reset_work_tree_to_interested add_nested_sub && ( cd submodule_update && - git branch -t remove_sub1 origin/remove_sub1 && - replace_gitfile_with_git_dir sub1 && - rm -rf .git/modules/sub1 && - $command remove_sub1 && - test_superproject_content origin/remove_sub1 && - test_git_directory_exists sub1 && - ! test -e sub1 + git branch -t modify_sub1_recursively origin/modify_sub1_recursively && + $command modify_sub1_recursively && + test_superproject_content origin/modify_sub1_recursively && + test_submodule_content sub1 origin/modify_sub1_recursively && + test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively + ) + ' +} + +# Same as test_submodule_switch_recursing_with_args(), except that throwing +# away local changes in the superproject is allowed. +test_submodule_forced_switch_recursing_with_args () { + cmd_args="$1" + command="git $cmd_args --recurse-submodules" + test_submodule_recursing_with_args_common "$command" + + RESULT=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULT=failure + fi + # Switching to a commit letting a submodule appear does not care about + # an untracked file. + test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && + test_submodule_content sub1 origin/add_sub1 ) ' + # Replacing a submodule with files in a directory ... test_expect_success "$command: replace submodule with a directory" ' prolog && @@ -997,17 +972,6 @@ test_submodule_forced_switch_recursing_with_args () { test_git_directory_exists sub1 ) ' - # Replacing it with a file - test_expect_success "$command: replace submodule with a file" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t replace_sub1_with_file origin/replace_sub1_with_file && - $command replace_sub1_with_file && - test_superproject_content origin/replace_sub1_with_file - ) - ' # ... even if the submodule contains ignored files test_expect_success "$command: replace submodule with a file ignoring ignored files" ' @@ -1022,46 +986,6 @@ test_submodule_forced_switch_recursing_with_args () { ) ' - # ... but stops for untracked files that would be lost - test_expect_$RESULT "$command: replace submodule with a file stops for untracked files" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t replace_sub1_with_file origin/replace_sub1_with_file && - : >sub1/untracked_file && - test_must_fail $command replace_sub1_with_file && - test_superproject_content origin/add_sub1 && - test -f sub1/untracked_file - ) - ' - - ########################## Modified submodule ######################### - # Updating a submodule sha1 updates the submodule's work tree - test_expect_success "$command: modified submodule updates submodule work tree" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t modify_sub1 origin/modify_sub1 && - $command modify_sub1 && - test_superproject_content origin/modify_sub1 && - test_submodule_content sub1 origin/modify_sub1 - ) - ' - # Updating a submodule to an invalid sha1 doesn't update the - # submodule's work tree, subsequent update will fail - test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" ' - prolog && - reset_work_tree_to_interested add_sub1 && - ( - cd submodule_update && - git branch -t invalid_sub1 origin/invalid_sub1 && - test_must_fail $command invalid_sub1 && - test_superproject_content origin/add_sub1 && - test_submodule_content sub1 origin/add_sub1 - ) - ' # Updating a submodule from an invalid sha1 updates test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" ' prolog && diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 1dbc85b214..e401208488 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -69,12 +69,17 @@ if (not @tests) { @tests = glob "p????-*.sh"; } +my $resultsdir = "test-results"; +if ($ENV{GIT_PERF_SUBSECTION} ne "") { + $resultsdir .= "/" . $ENV{GIT_PERF_SUBSECTION}; +} + my @subtests; my %shorttests; for my $t (@tests) { $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t"; my $n = $2; - my $fname = "test-results/$t.subtests"; + my $fname = "$resultsdir/$t.subtests"; open my $fp, "<", $fname or die "cannot open $fname: $!"; for (<$fp>) { chomp; @@ -98,7 +103,7 @@ sub read_descr { my %descrs; my $descrlen = 4; # "Test" for my $t (@subtests) { - $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr"); + $descrs{$t} = $shorttests{$t}.": ".read_descr("$resultsdir/$t.descr"); $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen; } @@ -138,7 +143,7 @@ for my $t (@subtests) { my $firstr; for my $i (0..$#dirs) { my $d = $dirs[$i]; - $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")]; + $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")]; my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; my $w = length format_times($r,$u,$s,$firstr); $colwidth[$i] = $w if $w > $colwidth[$i]; diff --git a/t/perf/lib-pack.sh b/t/perf/lib-pack.sh new file mode 100644 index 0000000000..d3865db286 --- /dev/null +++ b/t/perf/lib-pack.sh @@ -0,0 +1,25 @@ +# Helpers for dealing with large numbers of packs. + +# create $1 nonsense packs, each with a single blob +create_packs () { + perl -le ' + my ($n) = @ARGV; + for (1..$n) { + print "blob"; + print "data <<EOF"; + print "$_"; + print "EOF"; + print "checkpoint" + } + ' "$@" | + git fast-import +} + +# create a large number of packs, disabling any gc which might +# cause us to repack them +setup_many_packs () { + git config gc.auto 0 && + git config gc.autopacklimit 0 && + git config fastimport.unpacklimit 0 && + create_packs 500 +} diff --git a/t/perf/p4211-line-log.sh b/t/perf/p4211-line-log.sh index e0ed05907c..392bcc0e51 100755 --- a/t/perf/p4211-line-log.sh +++ b/t/perf/p4211-line-log.sh @@ -35,4 +35,8 @@ test_perf 'git log --oneline --raw --parents' ' git log --oneline --raw --parents >/dev/null ' +test_perf 'git log --oneline --raw --parents -1000' ' + git log --oneline --raw --parents -1000 >/dev/null +' + test_done diff --git a/t/perf/p5550-fetch-tags.sh b/t/perf/p5550-fetch-tags.sh index a5dc39f86a..d0e0e019ea 100755 --- a/t/perf/p5550-fetch-tags.sh +++ b/t/perf/p5550-fetch-tags.sh @@ -20,6 +20,7 @@ start to show a noticeable performance problem on my machine, but without taking too long to set up and run the tests. ' . ./perf-lib.sh +. "$TEST_DIRECTORY/perf/lib-pack.sh" # make a long nonsense history on branch $1, consisting of $2 commits, each # with a unique file pointing to the blob at $2. @@ -44,26 +45,6 @@ create_tags () { git update-ref --stdin } -# create $1 nonsense packs, each with a single blob -create_packs () { - perl -le ' - my ($n) = @ARGV; - for (1..$n) { - print "blob"; - print "data <<EOF"; - print "$_"; - print "EOF"; - } - ' "$@" | - git fast-import && - - git cat-file --batch-all-objects --batch-check='%(objectname)' | - while read sha1 - do - echo $sha1 | git pack-objects .git/objects/pack/pack - done -} - test_expect_success 'create parent and child' ' git init parent && git -C parent commit --allow-empty -m base && @@ -84,9 +65,7 @@ test_expect_success 'populate parent tags' ' test_expect_success 'create child packs' ' ( cd child && - git config gc.auto 0 && - git config gc.autopacklimit 0 && - create_packs 500 + setup_many_packs ) ' diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh new file mode 100755 index 0000000000..b99dc23e32 --- /dev/null +++ b/t/perf/p5551-fetch-rescan.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='fetch performance with many packs + +It is common for fetch to consider objects that we might not have, and it is an +easy mistake for the code to use a function like `parse_object` that might +give the correct _answer_ on such an object, but do so slowly (due to +re-scanning the pack directory for lookup failures). + +The resulting performance drop can be hard to notice in a real repository, but +becomes quite large in a repository with a large number of packs. So this +test creates a more pathological case, since any mistakes would produce a more +noticeable slowdown. +' +. ./perf-lib.sh +. "$TEST_DIRECTORY"/perf/lib-pack.sh + +test_expect_success 'create parent and child' ' + git init parent && + git clone parent child +' + + +test_expect_success 'create refs in the parent' ' + ( + cd parent && + git commit --allow-empty -m foo && + head=$(git rev-parse HEAD) && + test_seq 1000 | + sed "s,.*,update refs/heads/& $head," | + $MODERN_GIT update-ref --stdin + ) +' + +test_expect_success 'create many packs in the child' ' + ( + cd child && + setup_many_packs + ) +' + +test_perf 'fetch' ' + # start at the same state for each iteration + obj=$($MODERN_GIT -C parent rev-parse HEAD) && + ( + cd child && + $MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes | + $MODERN_GIT update-ref --stdin && + rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") && + + git fetch + ) +' + +test_done diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh index 16d1bf72e5..65e145c02d 100755 --- a/t/perf/p7519-fsmonitor.sh +++ b/t/perf/p7519-fsmonitor.sh @@ -40,8 +40,7 @@ test_lazy_prereq UNTRACKED_CACHE ' ' test_lazy_prereq WATCHMAN ' - { command -v watchman >/dev/null 2>&1; ret=$?; } && - test $ret -ne 1 + command -v watchman ' if test_have_prereq WATCHMAN diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index b50211b259..e4c343a6b7 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -56,12 +56,10 @@ MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git export MODERN_GIT perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results +test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION" mkdir -p "$perf_results_dir" rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests -if test -z "$GIT_PERF_REPEAT_COUNT"; then - GIT_PERF_REPEAT_COUNT=3 -fi die_if_build_dir_not_repo () { if ! ( cd "$TEST_DIRECTORY/.." && git rev-parse --build-dir >/dev/null 2>&1 ); then diff --git a/t/perf/run b/t/perf/run index beb4acc0e4..43e4de49ef 100755 --- a/t/perf/run +++ b/t/perf/run @@ -2,9 +2,14 @@ case "$1" in --help) - echo "usage: $0 [other_git_tree...] [--] [test_scripts]" + echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]" exit 0 ;; + --config) + shift + GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") + export GIT_PERF_CONFIG_FILE + shift ;; esac die () { @@ -29,8 +34,10 @@ unpack_git_rev () { (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) | (cd build/$rev && tar x) } + build_git_rev () { rev=$1 + name="$2" for config in config.mak config.mak.autogen config.status do if test -e "../../$config" @@ -38,7 +45,7 @@ build_git_rev () { cp "../../$config" "build/$rev/" fi done - echo "=== Building $rev ===" + echo "=== Building $rev ($name) ===" ( cd build/$rev && if test -n "$GIT_PERF_MAKE_COMMAND" @@ -65,7 +72,7 @@ run_dirs_helper () { if [ ! -d build/$rev ]; then unpack_git_rev $rev fi - build_git_rev $rev + build_git_rev $rev "$mydir" mydir=build/$rev fi if test "$mydir" = .; then @@ -87,14 +94,78 @@ run_dirs () { done } -GIT_PERF_AGGREGATING_LATER=t -export GIT_PERF_AGGREGATING_LATER +get_subsections () { + section="$1" + test -z "$GIT_PERF_CONFIG_FILE" && return + git config -f "$GIT_PERF_CONFIG_FILE" --name-only --get-regex "$section\..*\.[^.]+" | + sed -e "s/$section\.\(.*\)\..*/\1/" | sort | uniq +} + +get_var_from_env_or_config () { + env_var="$1" + conf_sec="$2" + conf_var="$3" + # $4 can be set to a default value + + # Do nothing if the env variable is already set + eval "test -z \"\${$env_var+x}\"" || return + + test -z "$GIT_PERF_CONFIG_FILE" && return + + # Check if the variable is in the config file + if test -n "$GIT_PERF_SUBSECTION" + then + var="$conf_sec.$GIT_PERF_SUBSECTION.$conf_var" + conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") && + eval "$env_var=\"$conf_value\"" && return + fi + var="$conf_sec.$conf_var" + conf_value=$(git config -f "$GIT_PERF_CONFIG_FILE" "$var") && + eval "$env_var=\"$conf_value\"" && return + + test -n "${4+x}" && eval "$env_var=\"$4\"" +} + +run_subsection () { + get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" 3 + export GIT_PERF_REPEAT_COUNT + + get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs" + set -- $GIT_PERF_DIRS_OR_REVS "$@" + + get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand" + get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts" + + GIT_PERF_AGGREGATING_LATER=t + export GIT_PERF_AGGREGATING_LATER + + if test $# = 0 -o "$1" = -- -o -f "$1"; then + set -- . "$@" + fi + + run_dirs "$@" + ./aggregate.perl "$@" +} cd "$(dirname $0)" . ../../GIT-BUILD-OPTIONS -if test $# = 0 -o "$1" = -- -o -f "$1"; then - set -- . "$@" +mkdir -p test-results +get_subsections "perf" >test-results/run_subsections.names + +if test $(wc -l <test-results/run_subsections.names) -eq 0 +then + ( + run_subsection "$@" + ) +else + while read -r subsec + do + ( + GIT_PERF_SUBSECTION="$subsec" + export GIT_PERF_SUBSECTION + echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" + run_subsection "$@" + ) + done <test-results/run_subsections.names fi -run_dirs "$@" -./aggregate.perl "$@" diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl index 6fd7fa476b..470107248e 100644 --- a/t/t0021/rot13-filter.pl +++ b/t/t0021/rot13-filter.pl @@ -31,7 +31,22 @@ # use 5.008; -use lib (split(/:/, $ENV{GITPERLLIB})); +sub gitperllib { + # Git assumes that all path lists are Unix-y colon-separated ones. But + # when the Git for Windows executes the test suite, its MSYS2 Bash + # calls git.exe, and colon-separated path lists are converted into + # Windows-y semicolon-separated lists of *Windows* paths (which + # naturally contain a colon after the drive letter, so splitting by + # colons simply does not cut it). + # + # Detect semicolon-separated path list and handle them appropriately. + + if ($ENV{GITPERLLIB} =~ /;/) { + return split(/;/, $ENV{GITPERLLIB}); + } + return split(/:/, $ENV{GITPERLLIB}); +} +use lib (gitperllib()); use strict; use warnings; use IO::File; @@ -70,7 +85,7 @@ print $debug "init handshake complete\n"; $debug->flush(); while (1) { - my ( $res, $command ) = packet_required_key_val_read("command"); + my ( $res, $command ) = packet_key_val_read("command"); if ( $res == -1 ) { print $debug "STOP\n"; exit(); @@ -106,7 +121,7 @@ while (1) { packet_txt_write("status=success"); packet_flush(); } else { - my ( $res, $pathname ) = packet_required_key_val_read("pathname"); + my ( $res, $pathname ) = packet_key_val_read("pathname"); if ( $res == -1 ) { die "unexpected EOF while expecting pathname"; } diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh new file mode 100755 index 0000000000..9d9e02a211 --- /dev/null +++ b/t/t0025-crlf-renormalize.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='CRLF renormalization' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.autocrlf false && + printf "LINEONE\nLINETWO\nLINETHREE\n" >LF.txt && + printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >CRLF.txt && + printf "LINEONE\r\nLINETWO\nLINETHREE\n" >CRLF_mix_LF.txt && + git add . && + git commit -m initial +' + +test_expect_success 'renormalize CRLF in repo' ' + echo "*.txt text=auto" >.gitattributes && + git add --renormalize "*.txt" && + cat >expect <<-\EOF && + i/lf w/crlf attr/text=auto CRLF.txt + i/lf w/lf attr/text=auto LF.txt + i/lf w/mixed attr/text=auto CRLF_mix_LF.txt + EOF + git ls-files --eol | + sed -e "s/ / /g" -e "s/ */ /g" | + sort >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 68108d956a..beb5927f77 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -43,19 +43,31 @@ create_gitattributes () { } >.gitattributes } -create_NNO_files () { +# Create 2 sets of files: +# The NNO files are "Not NOrmalized in the repo. We use CRLF_mix_LF and store +# it under different names for the different test cases, see ${pfx} +# Depending on .gitattributes they are normalized at the next commit (or not) +# The MIX files have different contents in the repo. +# Depending on its contents, the "new safer autocrlf" may kick in. +create_NNO_MIX_files () { for crlf in false true input do for attr in "" auto text -text do for aeol in "" lf crlf do - pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} + pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} && cp CRLF_mix_LF ${pfx}_LF.txt && cp CRLF_mix_LF ${pfx}_CRLF.txt && cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt && cp CRLF_mix_LF ${pfx}_LF_mix_CR.txt && - cp CRLF_mix_LF ${pfx}_CRLF_nul.txt + cp CRLF_mix_LF ${pfx}_CRLF_nul.txt && + pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} && + cp LF ${pfx}_LF.txt && + cp CRLF ${pfx}_CRLF.txt && + cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt && + cp LF_mix_CR ${pfx}_LF_mix_CR.txt && + cp CRLF_nul ${pfx}_CRLF_nul.txt done done done @@ -136,6 +148,49 @@ commit_chk_wrnNNO () { ' } +# Commit a file with mixed line endings on top of different files +# in the index. Check for warnings +commit_MIX_chkwrn () { + attr=$1 ; shift + aeol=$1 ; shift + crlf=$1 ; shift + lfwarn=$1 ; shift + crlfwarn=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} + #Commit file with CLRF_mix_LF on top of existing file + create_gitattributes "$attr" $aeol && + for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + do + fname=${pfx}_$f.txt && + cp CRLF_mix_LF $fname && + printf Z >>"$fname" && + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" + done + + test_expect_success "commit file with mixed EOL onto LF crlf=$crlf attr=$attr" ' + check_warning "$lfwarn" ${pfx}_LF.err + ' + test_expect_success "commit file with mixed EOL onto CLRF attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$crlfwarn" ${pfx}_CRLF.err + ' + + test_expect_success "commit file with mixed EOL onto CRLF_mix_LF attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err + ' + + test_expect_success "commit file with mixed EOL onto LF_mix_cr attr=$attr aeol=$aeol crlf=$crlf " ' + check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err + ' + + test_expect_success "commit file with mixed EOL onto CRLF_nul attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$crlfnul" ${pfx}_CRLF_nul.err + ' +} + + stats_ascii () { case "$1" in LF) @@ -323,8 +378,8 @@ test_expect_success 'setup master' ' printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\rLINETHREE" >CRLF_mix_CR && printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONEQ\r\nLINETWO\r\nLINETHREE" | q_to_nul >CRLF_nul && printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONEQ\nLINETWO\nLINETHREE" | q_to_nul >LF_nul && - create_NNO_files CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF && - git -c core.autocrlf=false add NNO_*.txt && + create_NNO_MIX_files && + git -c core.autocrlf=false add NNO_*.txt MIX_*.txt && git commit -m "mixed line endings" && test_tick ' @@ -385,6 +440,18 @@ test_expect_success 'commit files attr=crlf' ' commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" ' +# Commit "CRLFmixLF" on top of these files already in the repo: +# mixed mixed mixed mixed mixed +# onto onto onto onto onto +# attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL +commit_MIX_chkwrn "" "" false "" "" "" "" "" +commit_MIX_chkwrn "" "" true "LF_CRLF" "" "" "LF_CRLF" "LF_CRLF" +commit_MIX_chkwrn "" "" input "CRLF_LF" "" "" "CRLF_LF" "CRLF_LF" + +commit_MIX_chkwrn "auto" "" false "$WAMIX" "" "" "$WAMIX" "$WAMIX" +commit_MIX_chkwrn "auto" "" true "LF_CRLF" "" "" "LF_CRLF" "LF_CRLF" +commit_MIX_chkwrn "auto" "" input "CRLF_LF" "" "" "CRLF_LF" "CRLF_LF" + # attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL commit_chk_wrnNNO "" "" false "" "" "" "" "" commit_chk_wrnNNO "" "" true LF_CRLF "" "" "" "" diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh index 1d8d1f210b..d6b54e8c65 100755 --- a/t/t0302-credential-store.sh +++ b/t/t0302-credential-store.sh @@ -37,7 +37,7 @@ helper_test store unset XDG_CONFIG_HOME test_expect_success 'if custom xdg file exists, home and xdg files not created' ' - test_when_finished "rm -f $HOME/xdg/git/credentials" && + test_when_finished "rm -f \"$HOME/xdg/git/credentials\"" && test -s "$HOME/xdg/git/credentials" && test_path_is_missing "$HOME/.git-credentials" && test_path_is_missing "$HOME/.config/git/credentials" diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 364a537000..cbeb9bebee 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -901,6 +901,36 @@ test_expect_success 'get --path barfs on boolean variable' ' test_must_fail git config --get --path path.bool ' +test_expect_success 'get --expiry-date' ' + rel="3.weeks.5.days.00:00" && + rel_out="$rel ->" && + cat >.git/config <<-\EOF && + [date] + valid1 = "3.weeks.5.days 00:00" + valid2 = "Fri Jun 4 15:46:55 2010" + valid3 = "2017/11/11 11:11:11PM" + valid4 = "2017/11/10 09:08:07 PM" + valid5 = "never" + invalid1 = "abc" + EOF + cat >expect <<-EOF && + $(test-date timestamp $rel) + 1275666415 + 1510441871 + 1510348087 + 0 + EOF + { + echo "$rel_out $(git config --expiry-date date.valid1)" + git config --expiry-date date.valid2 && + git config --expiry-date date.valid3 && + git config --expiry-date date.valid4 && + git config --expiry-date date.valid5 + } >actual && + test_cmp expect actual && + test_must_fail git config --expiry-date date.invalid1 +' + cat > expect << EOF [quote] leading = " test" diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index e88349c8a0..c7878a60ed 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -331,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' ' grep "fatal: invalid ref format: ~a" err ' +test_expect_success 'branch rejects HEAD as a branch name' ' + test_must_fail git branch HEAD HEAD^ && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'checkout -b rejects HEAD as a branch name' ' + test_must_fail git checkout -B HEAD HEAD^ && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'update-ref can operate on refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git show-ref refs/heads/HEAD && + git update-ref -d refs/heads/HEAD && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'branch -d can remove refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git branch -d HEAD && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'branch -m can rename refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git branch -m HEAD tail && + test_must_fail git show-ref refs/heads/HEAD && + git show-ref refs/heads/tail +' + +test_expect_success 'branch -d can remove refs/heads/-dash' ' + git update-ref refs/heads/-dash HEAD^ && + git branch -d -- -dash && + test_must_fail git show-ref refs/heads/-dash +' + +test_expect_success 'branch -m can rename refs/heads/-dash' ' + git update-ref refs/heads/-dash HEAD^ && + git branch -m -- -dash dash && + test_must_fail git show-ref refs/heads/-dash && + git show-ref refs/heads/dash +' + test_done diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index fbb4ee9bb4..bb4f2e0c63 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -186,4 +186,127 @@ test_expect_success 'no advice given for explicit detached head state' ' test_cmp expect.no-advice actual ' +# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format) +test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' " + + # The first detach operation is more chatty than the following ones. + cat >1st_detach <<-'EOF' && + Note: checking out 'HEAD^'. + + You are in 'detached HEAD' state. You can look around, make experimental + changes and commit them, and you can discard any commits you make in this + state without impacting any branches by performing another checkout. + + If you want to create a new branch to retain commits you create, you may + do so (now or later) by using -b with the checkout command again. Example: + + git checkout -b <new-branch-name> + + HEAD is now at 7c7cd714e262 three + EOF + + # The remaining ones just show info about previous and current HEADs. + cat >2nd_detach <<-'EOF' && + Previous HEAD position was 7c7cd714e262 three + HEAD is now at 139b20d8e6c5 two + EOF + + cat >3rd_detach <<-'EOF' && + Previous HEAD position was 139b20d8e6c5 two + HEAD is now at d79ce1670bdc one + EOF + + reset && + check_not_detached && + + # Various ways of *not* asking for ellipses + + sane_unset GIT_PRINT_SHA1_ELLIPSIS && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS="no" git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS= git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + sane_unset GIT_PRINT_SHA1_ELLIPSIS && + + # We only have four commits, but we can re-use them + reset && + check_not_detached && + + # Make no mention of the env var at all + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS='nope' && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=nein && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + true +" + +# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format) +test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' " + + # The first detach operation is more chatty than the following ones. + cat >1st_detach <<-'EOF' && + Note: checking out 'HEAD^'. + + You are in 'detached HEAD' state. You can look around, make experimental + changes and commit them, and you can discard any commits you make in this + state without impacting any branches by performing another checkout. + + If you want to create a new branch to retain commits you create, you may + do so (now or later) by using -b with the checkout command again. Example: + + git checkout -b <new-branch-name> + + HEAD is now at 7c7cd714e262... three + EOF + + # The remaining ones just show info about previous and current HEADs. + cat >2nd_detach <<-'EOF' && + Previous HEAD position was 7c7cd714e262... three + HEAD is now at 139b20d8e6c5... two + EOF + + cat >3rd_detach <<-'EOF' && + Previous HEAD position was 139b20d8e6c5... two + HEAD is now at d79ce1670bdc... one + EOF + + reset && + check_not_detached && + + # Various ways of asking for ellipses... + # The user can just use any kind of quoting (including none). + + GIT_PRINT_SHA1_ELLIPSIS=yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=Yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=YES git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + true +" + test_done diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 6ce9b9c070..2b95944973 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -245,6 +245,12 @@ test_expect_success 'local clone from linked checkout' ' ( cd here-clone && git fsck ) ' +test_expect_success 'local clone --shared from linked checkout' ' + git -C bare worktree add --detach ../baretree && + git clone --local --shared baretree bare-clone && + grep /bare/ bare-clone/.git/objects/info/alternates +' + test_expect_success '"add" worktree with --no-checkout' ' git worktree add --no-checkout -b swamp swamp && ! test -e swamp/init.t && @@ -444,4 +450,33 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' ' ) ' +post_checkout_hook () { + test_when_finished "rm -f .git/hooks/post-checkout" && + mkdir -p .git/hooks && + write_script .git/hooks/post-checkout <<-\EOF + echo $* >hook.actual + EOF +} + +test_expect_success '"add" invokes post-checkout hook (branch)' ' + post_checkout_hook && + printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect && + git worktree add gumby && + test_cmp hook.expect hook.actual +' + +test_expect_success '"add" invokes post-checkout hook (detached)' ' + post_checkout_hook && + printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect && + git worktree add --detach grumpy && + test_cmp hook.expect hook.actual +' + +test_expect_success '"add --no-checkout" suppresses post-checkout hook' ' + post_checkout_hook && + rm -f hook.actual && + git worktree add --no-checkout gloopy && + test_path_is_missing hook.actual +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9a893b5fe7..cdc38fe5d1 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -678,4 +678,54 @@ test_expect_success 'merge-recursive remembers the names of all base trees' ' test_cmp expect actual ' +test_expect_success 'merge-recursive internal merge resolves to the sameness' ' + git reset --hard HEAD && + + # We are going to create a history leading to two criss-cross + # branches A and B. The common ancestor at the bottom, O0, + # has two child commits O1 and O2, both of which will be merge + # base between A and B, like so: + # + # O1---A + # / \ / + # O0 . + # \ / \ + # O2---B + # + # The recently added "check to see if the index is different from + # the tree into which something else is getting merged" check must + # NOT kick in when an inner merge between O1 and O2 is made. Both + # O1 and O2 happen to have the same tree as O0 in this test to + # trigger the bug---whether the inner merge is made by merging O2 + # into O1 or O1 into O2, their common ancestor O0 and the branch + # being merged have the same tree. We should not trigger the "is + # the index dirty?" check in this case. + + echo "zero" >file && + git add file && + test_tick && + git commit -m "O0" && + O0=$(git rev-parse HEAD) && + + test_tick && + git commit --allow-empty -m "O1" && + O1=$(git rev-parse HEAD) && + + git reset --hard $O0 && + test_tick && + git commit --allow-empty -m "O2" && + O2=$(git rev-parse HEAD) && + + test_tick && + git merge -s ours $O1 && + B=$(git rev-parse HEAD) && + + git reset --hard $O1 && + test_tick && + git merge -s ours $O2 && + A=$(git rev-parse HEAD) && + + git merge $B +' + test_done diff --git a/t/t3040-subprojects-basic.sh b/t/t3040-subprojects-basic.sh index 0a4ff6d824..b81eb5fd6f 100755 --- a/t/t3040-subprojects-basic.sh +++ b/t/t3040-subprojects-basic.sh @@ -19,7 +19,7 @@ test_expect_success 'setup: create subprojects' ' git update-index --add sub1 && git add sub2 && git commit -q -m "subprojects added" && - git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && + GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && git branch save HEAD && cat >expected <<-\EOF && :000000 160000 00000... A sub1 diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index baef2d6924..9c1bf6eb3d 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -176,7 +176,7 @@ git rev-parse refs/notes/z > pre_merge_z test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && git config core.notesRef refs/notes/m && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -379,7 +379,7 @@ git rev-parse refs/notes/z > pre_merge_z test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && git config core.notesRef refs/notes/m && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -413,7 +413,7 @@ git rev-parse refs/notes/y > pre_merge_y git rev-parse refs/notes/z > pre_merge_z test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -494,7 +494,7 @@ cp expect_log_y expect_log_m test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh index b9c3bc2487..10bfc8b947 100755 --- a/t/t3320-notes-merge-worktrees.sh +++ b/t/t3320-notes-merge-worktrees.sh @@ -61,7 +61,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' ' ( cd worktree2 && git config core.notesRef refs/notes/x && - test_must_fail git notes merge z 2>&1 >out && + test_must_fail git notes merge z >out 2>&1 && test_i18ngrep "Automatic notes merge failed" out && grep -v "A notes merge into refs/notes/x is already in-progress in" out ) && diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index f5fd15e559..8ac58d5ea5 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -255,4 +255,26 @@ test_expect_success 'rebase commit with an ancient timestamp' ' grep "author .* 34567 +0600$" actual ' +test_expect_success 'rebase with "From " line in commit message' ' + git checkout -b preserve-from master~1 && + cat >From_.msg <<EOF && +Somebody embedded an mbox in a commit message + +This is from so-and-so: + +From a@b Mon Sep 17 00:00:00 2001 +From: John Doe <nobody@example.com> +Date: Sat, 11 Nov 2017 00:00:00 +0000 +Subject: not this message + +something +EOF + >From_ && + git add From_ && + git commit -F From_.msg && + git rebase master && + git log -1 --pretty=format:%B >out && + test_cmp From_.msg out +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 6a82d1ed87..481a350090 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1260,6 +1260,28 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' +test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' ' + rebase_setup_and_clean abbrevcmd && + test_commit "first" file1.txt "first line" first && + test_commit "second" file1.txt "another line" second && + test_commit "fixup! first" file2.txt "first line again" first_fixup && + test_commit "squash! second" file1.txt "another line here" second_squash && + cat >expected <<-EOF && + p $(git rev-list --abbrev-commit -1 first) first + f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first + x git show HEAD + p $(git rev-list --abbrev-commit -1 second) second + s $(git rev-list --abbrev-commit -1 second_squash) squash! second + x git show HEAD + EOF + git checkout abbrevcmd && + set_cat_todo_editor && + test_config rebase.abbreviateCommands true && + test_must_fail git rebase -i --exec "git show HEAD" \ + --autosquash master >actual && + test_cmp expected actual +' + test_expect_success 'static check of bad command' ' rebase_setup_and_clean bad-cmd && set_fake_editor && diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index fcfdd197bd..7c91a85f43 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -74,6 +74,20 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test -f funny.was.run ' +test_expect_success 'rebase passes merge strategy options correctly' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + test_commit theirs-to-merge && + git reset --hard HEAD^ && + test_commit some-commit && + test_tick && + git merge --no-ff theirs-to-merge && + FAKE_LINES="1 edit 2 3" git rebase -i -f -p -m \ + -s recursive --strategy-option=theirs HEAD~2 && + test_commit force-change && + git rebase --continue +' + test_expect_success 'setup rerere database' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index 6863b7bb6f..ce48c4fcca 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -10,4 +10,40 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 test_submodule_switch "git cherry-pick" +test_expect_success 'unrelated submodule/file conflict is ignored' ' + test_create_repo sub && + + touch sub/file && + git -C sub add file && + git -C sub commit -m "add a file in a submodule" && + + test_create_repo a_repo && + ( + cd a_repo && + >a_file && + git add a_file && + git commit -m "add a file" && + + git branch test && + git checkout test && + + mkdir sub && + >sub/content && + git add sub/content && + git commit -m "add a regular folder with name sub" && + + echo "123" >a_file && + git add a_file && + git commit -m "modify a file" && + + git checkout master && + + git submodule add ../sub sub && + git submodule update sub && + git commit -m "add a submodule info folder with name sub" && + + git cherry-pick test + ) +' + test_done diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 3b94283e35..b92ff95977 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -40,7 +40,7 @@ test_expect_success 'UTF-16 refused because of NULs' ' ' test_expect_success 'UTF-8 invalid characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 characters" >F && printf "Commit message\n\nInvalid surrogate:\355\240\200\n" \ >"$HOME/invalid" && @@ -49,7 +49,7 @@ test_expect_success 'UTF-8 invalid characters refused' ' ' test_expect_success 'UTF-8 overlong sequences rejected' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && rm -f "$HOME/stderr" "$HOME/invalid" && echo "UTF-8 overlong" >F && printf "\340\202\251ommit message\n\nThis is not a space:\300\240\n" \ @@ -59,7 +59,7 @@ test_expect_success 'UTF-8 overlong sequences rejected' ' ' test_expect_success 'UTF-8 non-characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 non-character 1" >F && printf "Commit message\n\nNon-character:\364\217\277\276\n" \ >"$HOME/invalid" && @@ -68,7 +68,7 @@ test_expect_success 'UTF-8 non-characters refused' ' ' test_expect_success 'UTF-8 non-characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 non-character 2." >F && printf "Commit message\n\nNon-character:\357\267\220\n" \ >"$HOME/invalid" && diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 3b1ac1971a..39c7f2ebd7 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -804,6 +804,99 @@ test_expect_success 'push -m shows right message' ' test_cmp expect actual ' +test_expect_success 'push -m also works without space' ' + >foo && + git add foo && + git stash push -m"unspaced test message" && + echo "stash@{0}: On master: unspaced test message" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store -m foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store -m "store m" $STASH_ID && + echo "stash@{0}: store m" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store -mfoo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store -m"store mfoo" $STASH_ID && + echo "stash@{0}: store mfoo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store --message=foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store --message="store message=foo" $STASH_ID && + echo "stash@{0}: store message=foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store --message foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store --message "store message foo" $STASH_ID && + echo "stash@{0}: store message foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push -mfoo uses right message' ' + >foo && + git add foo && + git stash push -m"test mfoo" && + echo "stash@{0}: On master: test mfoo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push --message foo is synonym for -mfoo' ' + >foo && + git add foo && + git stash push --message "test message foo" && + echo "stash@{0}: On master: test message foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push --message=foo is synonym for -mfoo' ' + >foo && + git add foo && + git stash push --message="test message=foo" && + echo "stash@{0}: On master: test message=foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push -m shows right message' ' + >foo && + git add foo && + git stash push -m "test m foo" && + echo "stash@{0}: On master: test m foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + test_expect_success 'create stores correct message' ' >foo && git add foo && diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 0d1fa45d25..eadf4f6244 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -230,4 +230,19 @@ test_expect_success 'rename pretty print common prefix and suffix overlap' ' test_i18ngrep " d/f/{ => f}/e " output ' +test_expect_success 'diff-tree -l0 defaults to a big rename limit, not zero' ' + test_write_lines line1 line2 line3 >myfile && + git add myfile && + git commit -m x && + + test_write_lines line1 line2 line4 >myotherfile && + git rm myfile && + git add myotherfile && + git commit -m x && + + git diff-tree -M -l0 HEAD HEAD^ >actual && + # Verify that a rename from myotherfile to myfile was detected + grep "myotherfile.*myfile" actual +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index c515e3e53f..f10798b2df 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -118,20 +118,37 @@ test_expect_success setup ' EOF V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g') -while read cmd +while read magic cmd do - case "$cmd" in - '' | '#'*) continue ;; + case "$magic" in + '' | '#'*) + continue ;; + :*) + magic=${magic#:} + label="$magic-$cmd" + case "$magic" in + noellipses) ;; + *) + die "bug in t4103: unknown magic $magic" ;; + esac ;; + *) + cmd="$magic $cmd" magic= + label="$cmd" ;; esac - test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g') + test=$(echo "$label" | sed -e 's|[/ ][/ ]*|_|g') pfx=$(printf "%04d" $test_count) expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" - test_expect_success "git $cmd" ' + test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" ' { - echo "\$ git $cmd" - git $cmd | + echo "$ git $cmd" + case "$magic" in + "") + GIT_PRINT_SHA1_ELLIPSIS=yes git $cmd ;; + noellipses) + git $cmd ;; + esac | sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \ -e "s/^\\(.*mixed; boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/" echo "\$" @@ -158,9 +175,12 @@ diff-tree -r --abbrev initial diff-tree -r --abbrev=4 initial diff-tree --root initial diff-tree --root --abbrev initial +:noellipses diff-tree --root --abbrev initial diff-tree --root -r initial diff-tree --root -r --abbrev initial +:noellipses diff-tree --root -r --abbrev initial diff-tree --root -r --abbrev=4 initial +:noellipses diff-tree --root -r --abbrev=4 initial diff-tree -p initial diff-tree --root -p initial diff-tree --patch-with-stat initial @@ -209,6 +229,7 @@ diff-tree -p master diff-tree -p -m master diff-tree -c master diff-tree -c --abbrev master +:noellipses diff-tree -c --abbrev master diff-tree --cc master # stat only should show the diffstat with the first parent diff-tree -c --stat master @@ -255,8 +276,10 @@ rev-list --parents HEAD rev-list --children HEAD whatchanged master +:noellipses whatchanged master whatchanged -p master whatchanged --root master +:noellipses whatchanged --root master whatchanged --root -p master whatchanged --patch-with-stat master whatchanged --root --patch-with-stat master @@ -266,6 +289,7 @@ whatchanged --root -c --patch-with-stat --summary master # improved by Timo's patch whatchanged --root --cc --patch-with-stat --summary master whatchanged -SF master +:noellipses whatchanged -SF master whatchanged -SF -p master log --patch-with-stat master -- dir/ @@ -284,6 +308,7 @@ show --stat side show --stat --summary side show --patch-with-stat side show --patch-with-raw side +:noellipses show --patch-with-raw side show --patch-with-stat --summary side format-patch --stdout initial..side @@ -311,8 +336,10 @@ diff -r --stat initial..side diff initial..side diff --patch-with-stat initial..side diff --patch-with-raw initial..side +:noellipses diff --patch-with-raw initial..side diff --patch-with-stat -r initial..side diff --patch-with-raw -r initial..side +:noellipses diff --patch-with-raw -r initial..side diff --name-status dir2 dir diff --no-index --name-status dir2 dir diff --no-index --name-status -- dir2 dir @@ -325,10 +352,14 @@ diff --dirstat initial rearrange diff --dirstat-by-file initial rearrange # No-index --abbrev and --no-abbrev diff --raw initial +:noellipses diff --raw initial diff --raw --abbrev=4 initial +:noellipses diff --raw --abbrev=4 initial diff --raw --no-abbrev initial diff --no-index --raw dir2 dir +:noellipses diff --no-index --raw dir2 dir diff --no-index --raw --abbrev=4 dir2 dir +:noellipses diff --no-index --raw --abbrev=4 dir2 dir diff --no-index --raw --no-abbrev dir2 dir EOF diff --git a/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial new file mode 100644 index 0000000000..4bdad4072e --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root --abbrev initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 040000 0000000 da7a33f A dir +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial new file mode 100644 index 0000000000..26fbfeb177 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root -r --abbrev=4 initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 100644 0000 35d2 A dir/sub +:000000 100644 0000 01e7 A file0 +:000000 100644 0000 01e7 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial new file mode 100644 index 0000000000..2ac8561191 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root -r --abbrev initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 100644 0000000 35d242b A dir/sub +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master new file mode 100644 index 0000000000..bb80f013b3 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master @@ -0,0 +1,5 @@ +$ git diff-tree -c --abbrev master +59d314ad6f356dd08601a4cd5e530381da3e3c64 +::100644 100644 100644 cead32e 7289e35 992913c MM dir/sub +::100644 100644 100644 b414108 f4615da 10a8a9f MM file0 +$ diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir new file mode 100644 index 0000000000..41b7baf0a5 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --abbrev=4 dir2 dir +:000000 100644 0000 0000 A dir/sub +$ diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir new file mode 100644 index 0000000000..0cf3a3efea --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw dir2 dir +:000000 100644 0000000 0000000 A dir/sub +$ diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side new file mode 100644 index 0000000000..8d1f1e3721 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side @@ -0,0 +1,36 @@ +$ git diff --patch-with-raw -r initial..side +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side new file mode 100644 index 0000000000..50d8aee4f7 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side @@ -0,0 +1,36 @@ +$ git diff --patch-with-raw initial..side +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial new file mode 100644 index 0000000000..8ae44d6c83 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff --raw --abbrev=4 initial +:100644 100644 35d2 9929 M dir/sub +:100644 100644 01e7 10a8 M file0 +:000000 100644 0000 b1e6 A file1 +:100644 000000 01e7 0000 D file2 +$ diff --git a/t/t4013/diff.noellipses-diff_--raw_initial b/t/t4013/diff.noellipses-diff_--raw_initial new file mode 100644 index 0000000000..0175bfb281 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--raw_initial @@ -0,0 +1,6 @@ +$ git diff --raw initial +:100644 100644 35d242b 992913c M dir/sub +:100644 100644 01e79c3 10a8a9f M file0 +:000000 100644 0000000 b1e6722 A file1 +:100644 000000 01e79c3 0000000 D file2 +$ diff --git a/t/t4013/diff.noellipses-show_--patch-with-raw_side b/t/t4013/diff.noellipses-show_--patch-with-raw_side new file mode 100644 index 0000000000..32fed3d576 --- /dev/null +++ b/t/t4013/diff.noellipses-show_--patch-with-raw_side @@ -0,0 +1,42 @@ +$ git show --patch-with-raw side +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-whatchanged_--root_master b/t/t4013/diff.noellipses-whatchanged_--root_master new file mode 100644 index 0000000000..c2cfd4e729 --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_--root_master @@ -0,0 +1,42 @@ +$ git whatchanged --root master +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +:000000 100644 0000000 b1e6722 A file1 + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +:100644 100644 35d242b 8422d40 M dir/sub +:100644 100644 01e79c3 b414108 M file0 +:100644 000000 01e79c3 0000000 D file2 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + +:000000 100644 0000000 35d242b A dir/sub +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-whatchanged_-SF_master b/t/t4013/diff.noellipses-whatchanged_-SF_master new file mode 100644 index 0000000000..b36ce5886e --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_-SF_master @@ -0,0 +1,9 @@ +$ git whatchanged -SF master +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +$ diff --git a/t/t4013/diff.noellipses-whatchanged_master b/t/t4013/diff.noellipses-whatchanged_master new file mode 100644 index 0000000000..55e500f2ed --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_master @@ -0,0 +1,32 @@ +$ git whatchanged master +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +:000000 100644 0000000 b1e6722 A file1 + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +:100644 100644 35d242b 8422d40 M dir/sub +:100644 100644 01e79c3 b414108 M file0 +:100644 000000 01e79c3 0000000 D file2 +$ diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 6c9a93b734..17df491a3a 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -106,6 +106,8 @@ test_expect_success 'another test, without options' ' git diff -w -b --ignore-space-at-eol >out && test_cmp expect out && + git diff -w --ignore-cr-at-eol >out && + test_cmp expect out && tr "Q_" "\015 " <<-\EOF >expect && diff --git a/x b/x @@ -128,6 +130,9 @@ test_expect_success 'another test, without options' ' git diff -b --ignore-space-at-eol >out && test_cmp expect out && + git diff -b --ignore-cr-at-eol >out && + test_cmp expect out && + tr "Q_" "\015 " <<-\EOF >expect && diff --git a/x b/x index d99af23..22d9f73 100644 @@ -145,6 +150,29 @@ test_expect_success 'another test, without options' ' CR at end EOF git diff --ignore-space-at-eol >out && + test_cmp expect out && + + git diff --ignore-space-at-eol --ignore-cr-at-eol >out && + test_cmp expect out && + + tr "Q_" "\015 " <<-\EOF >expect && + diff --git a/x b/x + index_d99af23..22d9f73 100644 + --- a/x + +++ b/x + @@ -1,6 +1,6 @@ + -whitespace at beginning + -whitespace change + -whitespace in the middle + -whitespace at end + +_ whitespace at beginning + +whitespace_ _change + +white space in the middle + +whitespace at end__ + unchanged line + CR at end + EOF + git diff --ignore-cr-at-eol >out && test_cmp expect out ' @@ -608,6 +636,23 @@ test_expect_success 'check with space before tab in indent (diff-tree)' ' test_must_fail git diff-tree --check HEAD^ HEAD ' +test_expect_success 'check with ignored trailing whitespace attr (diff-tree)' ' + test_when_finished "git reset --hard HEAD^" && + + # create a whitespace error that should be ignored + echo "* -whitespace" >.gitattributes && + git add .gitattributes && + echo "foo(); " >x && + git add x && + git commit -m "add trailing space" && + + # with a worktree diff-tree ignores the whitespace error + git diff-tree --root --check HEAD && + + # without a worktree diff-tree still ignores the whitespace error + git -C .git diff-tree --root --check HEAD +' + test_expect_success 'check trailing whitespace (trailing-space: off)' ' git config core.whitespace "-trailing-space" && echo "foo (); " >x && diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh index 3950f5034d..6471a68701 100755 --- a/t/t4045-diff-relative.sh +++ b/t/t4045-diff-relative.sh @@ -12,62 +12,76 @@ test_expect_success 'setup' ' git commit -m one ' -check_diff() { -expect=$1; shift -cat >expected <<EOF -diff --git a/$expect b/$expect -new file mode 100644 -index 0000000..25c05ef ---- /dev/null -+++ b/$expect -@@ -0,0 +1 @@ -+other content -EOF -test_expect_success "-p $*" " - git diff -p $* HEAD^ >actual && - test_cmp expected actual -" +check_diff () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + diff --git a/$expect b/$expect + new file mode 100644 + index 0000000..25c05ef + --- /dev/null + +++ b/$expect + @@ -0,0 +1 @@ + +other content + EOF + test_expect_success "-p $*" " + git -C '$dir' diff -p $* HEAD^ >actual && + test_cmp expected actual + " } -check_numstat() { -expect=$1; shift -cat >expected <<EOF -1 0 $expect -EOF -test_expect_success "--numstat $*" " - echo '1 0 $expect' >expected && - git diff --numstat $* HEAD^ >actual && - test_cmp expected actual -" +check_numstat () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + 1 0 $expect + EOF + test_expect_success "--numstat $*" " + echo '1 0 $expect' >expected && + git -C '$dir' diff --numstat $* HEAD^ >actual && + test_cmp expected actual + " } -check_stat() { -expect=$1; shift -cat >expected <<EOF - $expect | 1 + - 1 file changed, 1 insertion(+) -EOF -test_expect_success "--stat $*" " - git diff --stat $* HEAD^ >actual && - test_i18ncmp expected actual -" +check_stat () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + $expect | 1 + + 1 file changed, 1 insertion(+) + EOF + test_expect_success "--stat $*" " + git -C '$dir' diff --stat $* HEAD^ >actual && + test_i18ncmp expected actual + " } -check_raw() { -expect=$1; shift -cat >expected <<EOF -:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect -EOF -test_expect_success "--raw $*" " - git diff --no-abbrev --raw $* HEAD^ >actual && - test_cmp expected actual -" +check_raw () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect + EOF + test_expect_success "--raw $*" " + git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual && + test_cmp expected actual + " } -for type in diff numstat stat raw; do - check_$type file2 --relative=subdir/ - check_$type file2 --relative=subdir - check_$type dir/file2 --relative=sub +for type in diff numstat stat raw +do + check_$type . file2 --relative=subdir/ + check_$type . file2 --relative=subdir + check_$type subdir file2 --relative + check_$type . dir/file2 --relative=sub done test_done diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 3e6b485ecb..2d76a971c4 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -85,6 +85,10 @@ test_expect_success 'setup' ' check_diff changed_hello 'changed function' +test_expect_success ' context includes comment' ' + grep "^ .*Hello comment" changed_hello.diff +' + test_expect_success ' context includes begin' ' grep "^ .*Begin of hello" changed_hello.diff ' diff --git a/t/t4051/hello.c b/t/t4051/hello.c index 63b1a1e4ef..73e767e178 100644 --- a/t/t4051/hello.c +++ b/t/t4051/hello.c @@ -1,4 +1,7 @@ +/* + * Hello comment. + */ static void hello(void) // Begin of hello { /* diff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh new file mode 100755 index 0000000000..b3f510f040 --- /dev/null +++ b/t/t4065-diff-anchored.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +test_description='anchored diff algorithm' + +. ./test-lib.sh + +test_expect_success '--anchored' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + # normally, c is moved to produce the smallest diff + test_expect_code 1 git diff --no-index pre post >diff && + grep "^+c" diff && + + # with anchor, a is moved + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+a" diff +' + +test_expect_success '--anchored multiple' ' + printf "a\nb\nc\nd\ne\nf\n" >pre && + printf "c\na\nb\nf\nd\ne\n" >post && + + # with 1 anchor, c is not moved, but f is moved + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+a" diff && # a is moved instead of c + grep "^+f" diff && + + # with 2 anchors, c and f are not moved + test_expect_code 1 git diff --no-index --anchored=c --anchored=f pre post >diff && + grep "^+a" diff && + grep "^+d" diff # d is moved instead of f +' + +test_expect_success '--anchored with nonexistent line has no effect' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --anchored=x pre post >diff && + grep "^+c" diff +' + +test_expect_success '--anchored with non-unique line has no effect' ' + printf "a\nb\nc\nd\ne\nc\n" >pre && + printf "c\na\nb\nc\nd\ne\n" >post && + + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+c" diff +' + +test_expect_success 'diff still produced with impossible multiple --anchored' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --anchored=a --anchored=c pre post >diff && + mv post expected_post && + + # Ensure that the diff is correct by applying it and then + # comparing the result with the original + git apply diff && + diff expected_post post +' + +test_expect_success 'later algorithm arguments override earlier ones' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --patience --anchored=c pre post >diff && + grep "^+a" diff && + + test_expect_code 1 git diff --no-index --anchored=c --patience pre post >diff && + grep "^+c" diff && + + test_expect_code 1 git diff --no-index --histogram --anchored=c pre post >diff && + grep "^+a" diff && + + test_expect_code 1 git diff --no-index --anchored=c --histogram pre post >diff && + grep "^+c" diff +' + +test_expect_success '--anchored works with other commands like "git show"' ' + printf "a\nb\nc\n" >file && + git add file && + git commit -m foo && + printf "c\na\nb\n" >file && + git add file && + git commit -m foo && + + # with anchor, a is moved + git show --patience --anchored=c >diff && + grep "^+a" diff +' + +test_done diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh index 9e29b5262d..ac72eeaf27 100755 --- a/t/t4107-apply-ignore-whitespace.sh +++ b/t/t4107-apply-ignore-whitespace.sh @@ -178,4 +178,18 @@ test_expect_success 'patch5 fails (--no-ignore-whitespace)' ' test_must_fail git apply --no-ignore-whitespace patch5.patch ' +test_expect_success 'apply --ignore-space-change --inaccurate-eof' ' + echo 1 >file && + git apply --ignore-space-change --inaccurate-eof <<-\EOF && + diff --git a/file b/file + --- a/file + +++ b/file + @@ -1 +1 @@ + -1 + +2 + EOF + printf 2 >expect && + test_cmp expect file +' + test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 8f155da7a5..25b1f8cc73 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -737,6 +737,107 @@ test_expect_success 'log.decorate configuration' ' ' +test_expect_success 'decorate-refs with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/octopus*" \ + --decorate-refs="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (tag: reach, reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b, octopus-b) + octopus-a (tag: octopus-a, octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="heads/octopus*" \ + --decorate-refs-exclude="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs and decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (master) + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/*" \ + --decorate-refs-exclude="heads/oc*" >actual && + test_cmp expect.decorate actual +' + test_expect_success 'log.decorate config parsing' ' git log --oneline --decorate=full >expect.full && git log --oneline --decorate=short >expect.short && diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index 935df6a65c..a1705f70cf 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -93,4 +93,23 @@ test_expect_success 'command line pathspec parsing for "git log"' ' git log --merge -- a ' +test_expect_success 'tree_entry_interesting does not match past submodule boundaries' ' + test_when_finished "rm -rf repo submodule" && + git init submodule && + test_commit -C submodule initial && + git init repo && + >"repo/[bracket]" && + git -C repo add "[bracket]" && + test_tick && + git -C repo commit -m bracket && + git -C repo rev-list HEAD -- "[bracket]" >expect && + + git -C repo submodule add ../submodule && + test_tick && + git -C repo commit -m submodule && + + git -C repo rev-list HEAD -- "[bracket]" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh new file mode 100755 index 0000000000..1b0acc383b --- /dev/null +++ b/t/t5317-pack-objects-filter-objects.sh @@ -0,0 +1,375 @@ +#!/bin/sh + +test_description='git pack-objects using object filtering' + +. ./test-lib.sh + +# Test blob:none filter. + +test_expect_success 'setup r1' ' + echo "{print \$1}" >print_1.awk && + echo "{print \$2}" >print_2.awk && + + git init r1 && + for n in 1 2 3 4 5 + do + echo "This is file: $n" > r1/file.$n + git -C r1 add file.$n + git -C r1 commit -m "$n" + done +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r1 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r1 index-pack ../all.pack && + git -C r1 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:none packfile has no blobs' ' + git -C r1 pack-objects --rev --stdout --filter=blob:none >filter.pack <<-EOF && + HEAD + EOF + git -C r1 index-pack ../filter.pack && + git -C r1 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify normal and blob:none packfiles have same commits/trees' ' + git -C r1 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r1 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test blob:limit=<n>[kmg] filter. +# We boundary test around the size parameter. The filter is strictly less than +# the value, so size 500 and 1000 should have the same results, but 1001 should +# filter more. + +test_expect_success 'setup r2' ' + git init r2 && + for n in 1000 10000 + do + printf "%"$n"s" X > r2/large.$n + git -C r2 add large.$n + git -C r2 commit -m "$n" + done +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../all.pack && + git -C r2 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=500 omits all blobs' ' + git -C r2 pack-objects --rev --stdout --filter=blob:limit=500 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify blob:limit=1000' ' + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1000 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify blob:limit=1001' ' + git -C r2 ls-files -s large.1000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=10001' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1k' ' + git -C r2 ls-files -s large.1000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1m' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' ' + git -C r2 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r2 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:path=<path> filter. +# Use a local file containing a sparse-checkout specification to filter +# out blobs not required for the corresponding sparse-checkout. We do not +# require sparse-checkout to actually be enabled. + +test_expect_success 'setup r3' ' + git init r3 && + mkdir r3/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r3/$n + git -C r3 add $n + echo "This is file: dir1/$n" > r3/dir1/$n + git -C r3 add dir1/$n + done && + git -C r3 commit -m "sparse" && + echo dir1/ >pattern1 && + echo sparse1 >pattern2 +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../all.pack && + git -C r3 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern1' ' + git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../filter.pack && + git -C r3 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' ' + git -C r3 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r3 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern2' ' + git -C r3 ls-files -s sparse1 dir1/sparse1 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../filter.pack && + git -C r3 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' ' + git -C r3 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r3 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:oid=<oid-ish> filter. +# Like sparse:path, but we get the sparse-checkout specification from +# a blob rather than a file on disk. + +test_expect_success 'setup r4' ' + git init r4 && + mkdir r4/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r4/$n + git -C r4 add $n + echo "This is file: dir1/$n" > r4/dir1/$n + git -C r4 add dir1/$n + done && + echo dir1/ >r4/pattern && + git -C r4 add pattern && + git -C r4 commit -m "pattern" +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r4 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../all.pack && + git -C r4 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=OID' ' + git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) && + git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../filter.pack && + git -C r4 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=oid-ish' ' + git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../filter.pack && + git -C r4 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Delete some loose objects and use pack-objects, but WITHOUT any filtering. +# This models previously omitted objects that we did not receive. + +test_expect_success 'setup r1 - delete loose blobs' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + for id in `cat expected | sed "s|..|&/|"` + do + rm r1/.git/objects/$id + done +' + +test_expect_success 'verify pack-objects fails w/ missing objects' ' + test_must_fail git -C r1 pack-objects --rev --stdout >miss.pack <<-EOF + HEAD + EOF +' + +test_expect_success 'verify pack-objects fails w/ --missing=error' ' + test_must_fail git -C r1 pack-objects --rev --stdout --missing=error >miss.pack <<-EOF + HEAD + EOF +' + +test_expect_success 'verify pack-objects w/ --missing=allow-any' ' + git -C r1 pack-objects --rev --stdout --missing=allow-any >miss.pack <<-EOF + HEAD + EOF +' + +test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh new file mode 100755 index 0000000000..9594e891f4 --- /dev/null +++ b/t/t5573-pull-verify-signatures.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='pull signature verification tests' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPG 'create repositories with signed commits' ' + echo 1 >a && git add a && + test_tick && git commit -m initial && + git tag initial && + + git clone . signed && + ( + cd signed && + echo 2 >b && git add b && + test_tick && git commit -S -m "signed" + ) && + + git clone . unsigned && + ( + cd unsigned && + echo 3 >c && git add c && + test_tick && git commit -m "unsigned" + ) && + + git clone . bad && + ( + cd bad && + echo 4 >d && git add d && + test_tick && git commit -S -m "bad" && + git cat-file commit HEAD >raw && + sed -e "s/bad/forged bad/" raw >forged && + git hash-object -w -t commit forged >forged.commit && + git checkout $(cat forged.commit) + ) && + + git clone . untrusted && + ( + cd untrusted && + echo 5 >e && git add e && + test_tick && git commit -SB7227189 -m "untrusted" + ) +' + +test_expect_success GPG 'pull unsigned commit with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures unsigned 2>pullerror && + test_i18ngrep "does not have a GPG signature" pullerror +' + +test_expect_success GPG 'pull commit with bad signature with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures bad 2>pullerror && + test_i18ngrep "has a bad GPG signature" pullerror +' + +test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror && + test_i18ngrep "has an untrusted GPG signature" pullerror +' + +test_expect_success GPG 'pull signed commit with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + git pull --verify-signatures signed >pulloutput && + test_i18ngrep "has a good GPG signature" pulloutput +' + +test_expect_success GPG 'pull commit with bad signature without verification' ' + test_when_finished "git reset --hard && git checkout initial" && + git pull --ff-only bad 2>pullerror +' + +test_expect_success GPG 'pull commit with bad signature with --no-verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_config pull.verifySignatures true && + git pull --ff-only --no-verify-signatures bad 2>pullerror +' + +test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 50e40abb11..0f895478f0 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -306,23 +306,21 @@ test_expect_success 'clone checking out a tag' ' test_cmp fetch.expected fetch.actual ' -setup_ssh_wrapper () { - test_expect_success 'setup ssh wrapper' ' - rm -f "$TRASH_DIRECTORY/ssh-wrapper$X" && - cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \ - "$TRASH_DIRECTORY/ssh-wrapper$X" && - GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" && - export GIT_SSH && - export TRASH_DIRECTORY && - >"$TRASH_DIRECTORY"/ssh-output - ' -} +test_expect_success 'set up ssh wrapper' ' + cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \ + "$TRASH_DIRECTORY/ssh$X" && + GIT_SSH="$TRASH_DIRECTORY/ssh$X" && + export GIT_SSH && + export TRASH_DIRECTORY && + >"$TRASH_DIRECTORY"/ssh-output +' copy_ssh_wrapper_as () { rm -f "${1%$X}$X" && - cp "$TRASH_DIRECTORY/ssh-wrapper$X" "${1%$X}$X" && + cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" && + test_when_finished "rm $(git rev-parse --sq-quote "${1%$X}$X")" && GIT_SSH="${1%$X}$X" && - export GIT_SSH + test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" } expect_ssh () { @@ -346,8 +344,6 @@ expect_ssh () { (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) } -setup_ssh_wrapper - test_expect_success 'clone myhost:src uses ssh' ' git clone myhost:src ssh-clone && expect_ssh myhost src @@ -364,9 +360,52 @@ test_expect_success 'bracketed hostnames are still ssh' ' expect_ssh "-p 123" myhost src ' -test_expect_success 'uplink is not treated as putty' ' +test_expect_success 'OpenSSH variant passes -4' ' + git clone -4 "[myhost:123]:src" ssh-ipv4-clone && + expect_ssh "-4 -p 123" myhost src +' + +test_expect_success 'variant can be overridden' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/putty" && + git -c ssh.variant=putty clone -4 "[myhost:123]:src" ssh-putty-clone && + expect_ssh "-4 -P 123" myhost src +' + +test_expect_success 'variant=auto picks based on basename' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone && + expect_ssh "-4 -P 123" myhost src +' + +test_expect_success 'simple does not support -4/-6' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" && + test_must_fail git clone -4 "myhost:src" ssh-4-clone-simple +' + +test_expect_success 'simple does not support port' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" && + test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-simple +' + +test_expect_success 'uplink is treated as simple' ' copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" && - git clone "[myhost:123]:src" ssh-bracket-clone-uplink && + test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-uplink && + git clone "myhost:src" ssh-clone-uplink && + expect_ssh myhost src +' + +test_expect_success 'OpenSSH-like uplink is treated as ssh' ' + write_script "$TRASH_DIRECTORY/uplink" <<-EOF && + if test "\$1" = "-G" + then + exit 0 + fi && + exec "\$TRASH_DIRECTORY/ssh$X" "\$@" + EOF + test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" && + GIT_SSH="$TRASH_DIRECTORY/uplink" && + test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" && + git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink && expect_ssh "-p 123" myhost src ' @@ -418,12 +457,14 @@ test_expect_success 'ssh.variant overrides plink detection' ' ' test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && GIT_SSH_VARIANT=plink \ git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 && expect_ssh "-P 123" myhost src ' test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && GIT_SSH_VARIANT=tortoiseplink \ git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 && expect_ssh "-batch -P 123" myhost src @@ -435,9 +476,6 @@ test_expect_success 'clean failure on broken quoting' ' git clone "[myhost:123]:src" sq-failure ' -# Reset the GIT_SSH environment variable for clone tests. -setup_ssh_wrapper - counter=0 # $1 url # $2 none|host diff --git a/t/t5603-clone-dirname.sh b/t/t5603-clone-dirname.sh index d5af758129..13b5e5eb9b 100755 --- a/t/t5603-clone-dirname.sh +++ b/t/t5603-clone-dirname.sh @@ -11,7 +11,9 @@ test_expect_success 'setup ssh wrapper' ' git upload-pack "$TRASH_DIRECTORY" EOF GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" && + GIT_SSH_VARIANT=ssh && export GIT_SSH && + export GIT_SSH_VARIANT && export TRASH_DIRECTORY ' diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh index d2d883f3a1..b4905b822c 100755 --- a/t/t5615-alternate-env.sh +++ b/t/t5615-alternate-env.sh @@ -7,9 +7,9 @@ check_obj () { alt=$1; shift while read obj expect do - echo "$obj" >&3 && - echo "$obj $expect" >&4 - done 3>input 4>expect && + echo "$obj" >&5 && + echo "$obj $expect" >&6 + done 5>input 6>expect && GIT_ALTERNATE_OBJECT_DIRECTORIES=$alt \ git "$@" cat-file --batch-check='%(objectname) %(objecttype)' \ <input >actual && diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh new file mode 100755 index 0000000000..ba86a44eb1 --- /dev/null +++ b/t/t5700-protocol-v1.sh @@ -0,0 +1,294 @@ +#!/bin/sh + +test_description='test git wire-protocol transition' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +# Test protocol v1 with 'git://' transport +# +. "$TEST_DIRECTORY"/lib-git-daemon.sh +start_git_daemon --export-all --enable=receive-pack +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent + +test_expect_success 'create repo to be served by git-daemon' ' + git init "$daemon_parent" && + test_commit -C "$daemon_parent" one +' + +test_expect_success 'clone with git:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "$GIT_DAEMON_URL/parent" daemon_child 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "clone> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with git:// using protocol v1' ' + test_commit -C "$daemon_parent" two && + + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + fetch 2>log && + + git -C daemon_child log -1 --format=%s origin/master >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with git:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + pull 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with git:// using protocol v1' ' + test_commit -C daemon_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "push> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "push< version 1" log +' + +stop_git_daemon + +# Test protocol v1 with 'file://' transport +# +test_expect_success 'create repo to be served by file:// transport' ' + git init file_parent && + test_commit -C file_parent one +' + +test_expect_success 'clone with file:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "file://$(pwd)/file_parent" file_child 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with file:// using protocol v1' ' + test_commit -C file_parent two && + + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + fetch 2>log && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with file:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + pull 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with file:// using protocol v1' ' + test_commit -C file_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "push< version 1" log +' + +# Test protocol v1 with 'ssh://' transport +# +test_expect_success 'setup ssh wrapper' ' + GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" && + export GIT_SSH && + GIT_SSH_VARIANT=ssh && + export GIT_SSH_VARIANT && + export TRASH_DIRECTORY && + >"$TRASH_DIRECTORY"/ssh-output +' + +expect_ssh () { + test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' && + echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" && + (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) +} + +test_expect_success 'create repo to be served by ssh:// transport' ' + git init ssh_parent && + test_commit -C ssh_parent one +' + +test_expect_success 'clone with ssh:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with ssh:// using protocol v1' ' + test_commit -C ssh_parent two && + + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + fetch 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s origin/master >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with ssh:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + pull 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with ssh:// using protocol v1' ' + test_commit -C ssh_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + expect_ssh git-receive-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "push< version 1" log +' + +# Test protocol v1 with 'http://' transport +# +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'create repo to be served by http:// transport' ' + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one +' + +test_expect_success 'clone with http:// using protocol v1' ' + GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \ + clone "$HTTPD_URL/smart/http_parent" http_child 2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "Git-Protocol: version=1" log && + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'fetch with http:// using protocol v1' ' + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + fetch 2>log && + + git -C http_child log -1 --format=%s origin/master >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'pull with http:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + pull 2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'push with http:// using protocol v1' ' + test_commit -C http_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + push origin HEAD:client_branch && #2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +stop_httpd + +test_done diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh index 01023486c5..23b86fb977 100755 --- a/t/t6044-merge-unrelated-index-changes.sh +++ b/t/t6044-merge-unrelated-index-changes.sh @@ -6,18 +6,21 @@ test_description="merges with unrelated index changes" # Testcase for some simple merges # A -# o-----o B +# o-------o B # \ -# \---o C +# \-----o C # \ -# \-o D +# \---o D # \ -# o E +# \-o E +# \ +# o F # Commit A: some file a # Commit B: adds file b, modifies end of a # Commit C: adds file c # Commit D: adds file d, modifies beginning of a # Commit E: renames a->subdir/a, adds subdir/e +# Commit F: empty commit test_expect_success 'setup trivial merges' ' test_seq 1 10 >a && @@ -29,6 +32,7 @@ test_expect_success 'setup trivial merges' ' git branch C && git branch D && git branch E && + git branch F && git checkout B && echo b >b && @@ -52,7 +56,10 @@ test_expect_success 'setup trivial merges' ' git mv a subdir/a && echo e >subdir/e && git add subdir && - test_tick && git commit -m E + test_tick && git commit -m E && + + git checkout F && + test_tick && git commit --allow-empty -m F ' test_expect_success 'ff update' ' @@ -105,6 +112,15 @@ test_expect_success 'recursive' ' test_must_fail git merge -s recursive C^0 ' +test_expect_success 'recursive, when merge branch matches merge base' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive F^0 +' + test_expect_success 'octopus, unrelated file touched' ' git reset --hard && git checkout B^0 && diff --git a/t/t6100-rev-list-in-order.sh b/t/t6100-rev-list-in-order.sh new file mode 100755 index 0000000000..b2bb0a7f61 --- /dev/null +++ b/t/t6100-rev-list-in-order.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='rev-list testing in-commit-order' + +. ./test-lib.sh + +test_expect_success 'setup a commit history with trees, blobs' ' + for x in one two three four + do + echo $x >$x && + git add $x && + git commit -m "add file $x" || + return 1 + done && + for x in four three + do + git rm $x && + git commit -m "remove $x" || + return 1 + done +' + +test_expect_success 'rev-list --in-commit-order' ' + git rev-list --in-commit-order --objects HEAD >actual.raw && + cut -c 1-40 >actual <actual.raw && + + git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF && + HEAD^{commit} + HEAD^{tree} + HEAD^{tree}:one + HEAD^{tree}:two + HEAD~1^{commit} + HEAD~1^{tree} + HEAD~1^{tree}:three + HEAD~2^{commit} + HEAD~2^{tree} + HEAD~2^{tree}:four + HEAD~3^{commit} + # HEAD~3^{tree} skipped, same as HEAD~1^{tree} + HEAD~4^{commit} + # HEAD~4^{tree} skipped, same as HEAD^{tree} + HEAD~5^{commit} + HEAD~5^{tree} + EOF + grep -v "#" >expect <expect.raw && + + test_cmp expect actual +' + +test_expect_success 'rev-list lists blobs and trees after commits' ' + git rev-list --objects HEAD >actual.raw && + cut -c 1-40 >actual <actual.raw && + + git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF && + HEAD^{commit} + HEAD~1^{commit} + HEAD~2^{commit} + HEAD~3^{commit} + HEAD~4^{commit} + HEAD~5^{commit} + HEAD^{tree} + HEAD^{tree}:one + HEAD^{tree}:two + HEAD~1^{tree} + HEAD~1^{tree}:three + HEAD~2^{tree} + HEAD~2^{tree}:four + # HEAD~3^{tree} skipped, same as HEAD~1^{tree} + # HEAD~4^{tree} skipped, same as HEAD^{tree} + HEAD~5^{tree} + EOF + grep -v "#" >expect <expect.raw && + + test_cmp expect actual +' + +test_done diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh new file mode 100755 index 0000000000..0a37dd5f97 --- /dev/null +++ b/t/t6112-rev-list-filters-objects.sh @@ -0,0 +1,225 @@ +#!/bin/sh + +test_description='git rev-list using object filtering' + +. ./test-lib.sh + +# Test the blob:none filter. + +test_expect_success 'setup r1' ' + echo "{print \$1}" >print_1.awk && + echo "{print \$2}" >print_2.awk && + + git init r1 && + for n in 1 2 3 4 5 + do + echo "This is file: $n" > r1/file.$n + git -C r1 add file.$n + git -C r1 commit -m "$n" + done +' + +test_expect_success 'verify blob:none omits all 5 blobs' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify emitted+omitted == all' ' + git -C r1 rev-list HEAD --objects \ + | awk -f print_1.awk \ + | sort >expected && + git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + + +# Test blob:limit=<n>[kmg] filter. +# We boundary test around the size parameter. The filter is strictly less than +# the value, so size 500 and 1000 should have the same results, but 1001 should +# filter more. + +test_expect_success 'setup r2' ' + git init r2 && + for n in 1000 10000 + do + printf "%"$n"s" X > r2/large.$n + git -C r2 add large.$n + git -C r2 commit -m "$n" + done +' + +test_expect_success 'verify blob:limit=500 omits all blobs' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify emitted+omitted == all' ' + git -C r2 rev-list HEAD --objects \ + | awk -f print_1.awk \ + | sort >expected && + git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1000' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1001' ' + git -C r2 ls-files -s large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1k' ' + git -C r2 ls-files -s large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1m' ' + cat </dev/null >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:path=<path> filter. +# Use a local file containing a sparse-checkout specification to filter +# out blobs not required for the corresponding sparse-checkout. We do not +# require sparse-checkout to actually be enabled. + +test_expect_success 'setup r3' ' + git init r3 && + mkdir r3/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r3/$n + git -C r3 add $n + echo "This is file: dir1/$n" > r3/dir1/$n + git -C r3 add dir1/$n + done && + git -C r3 commit -m "sparse" && + echo dir1/ >pattern1 && + echo sparse1 >pattern2 +' + +test_expect_success 'verify sparse:path=pattern1 omits top-level files' ' + git -C r3 ls-files -s sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' ' + git -C r3 ls-files -s sparse2 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:oid=<oid-ish> filter. +# Like sparse:path, but we get the sparse-checkout specification from +# a blob rather than a file on disk. + +test_expect_success 'setup r3 part 2' ' + echo dir1/ >r3/pattern && + git -C r3 add pattern && + git -C r3 commit -m "pattern" +' + +test_expect_success 'verify sparse:oid=OID omits top-level files' ' + git -C r3 ls-files -s pattern sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=oid-ish omits top-level files' ' + git -C r3 ls-files -s pattern sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Delete some loose objects and use rev-list, but WITHOUT any filtering. +# This models previously omitted objects that we did not receive. + +test_expect_success 'rev-list W/ --missing=print' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + for id in `cat expected | sed "s|..|&/|"` + do + rm r1/.git/objects/$id + done && + git -C r1 rev-list --quiet HEAD --missing=print --objects \ + | awk -f print_1.awk \ + | sed "s/?//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'rev-list W/O --missing fails' ' + test_must_fail git -C r1 rev-list --quiet --objects HEAD +' + +test_expect_success 'rev-list W/ missing=allow-any' ' + git -C r1 rev-list --quiet --missing=allow-any --objects HEAD +' + +test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 1c0e8659d9..3e3fb462a0 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -304,12 +304,46 @@ test_expect_success 'describe chokes on severely broken submodules' ' mv .git/modules/sub1/ .git/modules/sub_moved && test_must_fail git describe --dirty ' -test_expect_success 'describe ignoring a borken submodule' ' +test_expect_success 'describe ignoring a broken submodule' ' git describe --broken >out && test_when_finished "mv .git/modules/sub_moved .git/modules/sub1" && grep broken out ' +test_expect_success 'describe a blob at a directly tagged commit' ' + echo "make it a unique blob" >file && + git add file && git commit -m "content in file" && + git tag -a -m "latest annotated tag" unique-file && + git describe HEAD:file >actual && + echo "unique-file:file" >expect && + test_cmp expect actual +' + +test_expect_success 'describe a blob with its first introduction' ' + git commit --allow-empty -m "empty commit" && + git rm file && + git commit -m "delete blob" && + git revert HEAD && + git commit --allow-empty -m "empty commit" && + git describe HEAD:file >actual && + echo "unique-file:file" >expect && + test_cmp expect actual +' + +test_expect_success 'describe directly tagged blob' ' + git tag test-blob unique-file:file && + git describe test-blob >actual && + echo "unique-file:file" >expect && + # suboptimal: we rather want to see "test-blob" + test_cmp expect actual +' + +test_expect_success 'describe tag object' ' + git tag test-blob-1 -a -m msg unique-file:file && + test_must_fail git describe test-blob-1 2>actual && + test_i18ngrep "fatal: test-blob-1 is neither a commit nor blob" actual +' + test_expect_failure ULIMIT_STACK_SIZE 'name-rev works in a deep repo' ' i=1 && while test $i -lt 8000 diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 865168ec6a..f5f46a95b4 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -214,6 +214,44 @@ test_expect_success TTY 'git tag as alias respects pager.tag with -l' ' ! test -e paginated.out ' +test_expect_success TTY 'git branch defaults to paging' ' + rm -f paginated.out && + test_terminal git branch && + test -e paginated.out +' + +test_expect_success TTY 'git branch respects pager.branch' ' + rm -f paginated.out && + test_terminal git -c pager.branch=false branch && + ! test -e paginated.out +' + +test_expect_success TTY 'git branch respects --no-pager' ' + rm -f paginated.out && + test_terminal git --no-pager branch && + ! test -e paginated.out +' + +test_expect_success TTY 'git branch --edit-description ignores pager.branch' ' + rm -f paginated.out editor.used && + write_script editor <<-\EOF && + echo "New description" >"$1" + touch editor.used + EOF + EDITOR=./editor test_terminal git -c pager.branch branch --edit-description && + ! test -e paginated.out && + test -e editor.used +' + +test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' ' + rm -f paginated.out && + git branch other && + test_when_finished "git branch -D other" && + test_terminal git -c pager.branch branch --set-upstream-to=other && + test_when_finished "git branch --unset-upstream" && + ! test -e paginated.out +' + # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 5739d3ed23..1d33c5feb3 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -130,8 +130,8 @@ EOF test_expect_success 'commit message from template with whitespace issue' ' echo "content galore" >>foo && git add foo && - GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \ - --template "$TEMPLATE" && + GIT_EDITOR=\""$TEST_DIRECTORY"\"/t7500/add-whitespaced-content \ + git commit --template "$TEMPLATE" && commit_msg_is "commit message" ' diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index 8ae69a61c3..e797c74112 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -35,27 +35,72 @@ test_expect_success GPG 'create signed commits' ' ' test_expect_success GPG 'merge unsigned commit with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures side-unsigned 2>mergeerror && test_i18ngrep "does not have a GPG signature" mergeerror ' +test_expect_success GPG 'merge unsigned commit with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only side-unsigned 2>mergeerror && + test_i18ngrep "does not have a GPG signature" mergeerror +' + test_expect_success GPG 'merge commit with bad signature with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2>mergeerror && test_i18ngrep "has a bad GPG signature" mergeerror ' +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only $(cat forged.commit) 2>mergeerror && + test_i18ngrep "has a bad GPG signature" mergeerror +' + test_expect_success GPG 'merge commit with untrusted signature with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror && test_i18ngrep "has an untrusted GPG signature" mergeerror ' +test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only side-untrusted 2>mergeerror && + test_i18ngrep "has an untrusted GPG signature" mergeerror +' + test_expect_success GPG 'merge signed commit with verification' ' + test_when_finished "git reset --hard && git checkout initial" && git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput && test_i18ngrep "has a good GPG signature" mergeoutput ' +test_expect_success GPG 'merge signed commit with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + git merge --verbose --ff-only side-signed >mergeoutput && + test_i18ngrep "has a good GPG signature" mergeoutput +' + test_expect_success GPG 'merge commit with bad signature without verification' ' + test_when_finished "git reset --hard && git checkout initial" && + git merge $(cat forged.commit) +' + +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=false' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures false && git merge $(cat forged.commit) ' +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true and --no-verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + git merge --no-verify-signatures $(cat forged.commit) +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 2a6679c2f5..1797f632a3 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -60,6 +60,18 @@ test_expect_success setup ' echo " line with leading space3" echo "line without leading space2" } >space && + cat >hello.ps1 <<-\EOF && + # No-op. + function dummy() {} + + # Say hello. + function hello() { + echo "Hello world." + } # hello + + # Still a no-op. + function dummy() {} + EOF git add . && test_tick && git commit -m initial @@ -766,18 +778,27 @@ test_expect_success 'grep -W shows no trailing empty lines' ' test_cmp expected actual ' -cat >expected <<EOF -hello.c= printf("Hello world.\n"); -hello.c: return 0; -hello.c- /* char ?? */ -EOF - test_expect_success 'grep -W with userdiff' ' test_when_finished "rm -f .gitattributes" && - git config diff.custom.xfuncname "(printf.*|})$" && - echo "hello.c diff=custom" >.gitattributes && - git grep -W return >actual && - test_cmp expected actual + git config diff.custom.xfuncname "^function .*$" && + echo "hello.ps1 diff=custom" >.gitattributes && + git grep -W echo >function-context-userdiff-actual +' + +test_expect_success ' includes preceding comment' ' + grep "# Say hello" function-context-userdiff-actual +' + +test_expect_success ' includes function line' ' + grep "=function hello" function-context-userdiff-actual +' + +test_expect_success ' includes matching line' ' + grep ": echo" function-context-userdiff-actual +' + +test_expect_success ' includes last line of the function' ' + grep "} # hello" function-context-userdiff-actual ' for threads in $(test_seq 0 10) @@ -1110,6 +1131,12 @@ test_expect_success PCRE 'grep -P pattern' ' test_cmp expected actual ' +test_expect_success LIBPCRE2 "grep -P with (*NO_JIT) doesn't error out" ' + git grep -P "(*NO_JIT)\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual + +' + test_expect_success !PCRE 'grep -P pattern errors without PCRE' ' test_must_fail git grep -P "foo.*bar" ' diff --git a/t/t9004-example.sh b/t/t9004-example.sh new file mode 100755 index 0000000000..b28a028f55 --- /dev/null +++ b/t/t9004-example.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='check that example code compiles and runs' +. ./test-lib.sh + +test_expect_success 'decorate' ' + test-example-decorate +' + +test_done diff --git a/t/t9020-remote-svn.sh b/t/t9020-remote-svn.sh index 4d81ba1c2c..6fca08e5e3 100755 --- a/t/t9020-remote-svn.sh +++ b/t/t9020-remote-svn.sh @@ -25,8 +25,8 @@ init_git () { git init && #git remote add svnsim testsvn::sim:///$TEST_DIRECTORY/t9020/example.svnrdump # let's reuse an existing dump file!? - git remote add svnsim testsvn::sim://$TEST_DIRECTORY/t9154/svn.dump - git remote add svnfile testsvn::file://$TEST_DIRECTORY/t9154/svn.dump + git remote add svnsim "testsvn::sim://$TEST_DIRECTORY/t9154/svn.dump" + git remote add svnfile "testsvn::file://$TEST_DIRECTORY/t9154/svn.dump" } if test -e "$GIT_BUILD_DIR/git-remote-testsvn" diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 9f3ef8f2ef..ceaa5bad10 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -28,7 +28,7 @@ test_expect_success 'git-svn-HEAD is a real HEAD' ' git rev-parse --verify refs/heads/git-svn-HEAD^0 ' -svnrepo_escaped=$(echo $svnrepo | sed 's/ /%20/') +svnrepo_escaped=$(echo $svnrepo | sed 's/ /%20/g') test_expect_success 'initialize old-style (v0) git svn layout' ' mkdir -p "$GIT_DIR"/git-svn/info "$GIT_DIR"/svn/info && diff --git a/t/t9169-git-svn-dcommit-crlf.sh b/t/t9169-git-svn-dcommit-crlf.sh new file mode 100755 index 0000000000..54b1f61a2a --- /dev/null +++ b/t/t9169-git-svn-dcommit-crlf.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +test_description='git svn dcommit CRLF' +. ./lib-git-svn.sh + +test_expect_success 'setup commit repository' ' + svn_cmd mkdir -m "$test_description" "$svnrepo/dir" && + git svn clone "$svnrepo" work && + ( + cd work && + echo foo >>foo && + git update-index --add foo && + printf "a\\r\\n\\r\\nb\\r\\nc\\r\\n" >cmt && + p=$(git rev-parse HEAD) && + t=$(git write-tree) && + cmt=$(git commit-tree -p $p $t <cmt) && + git update-ref refs/heads/master $cmt && + git cat-file commit HEAD | tail -n4 >out && + test_cmp cmt out && + git svn dcommit && + printf "a\\n\\nb\\nc\\n" >exp && + git cat-file commit HEAD | sed -ne 6,9p >out && + test_cmp exp out + ) +' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index d47560b634..e4d06accc4 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -876,7 +876,7 @@ test_expect_success 'L: verify internal tree sorting' ' EXPECT_END git fast-import <input && - git diff-tree --abbrev --raw L^ L >output && + GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output && test_cmp expect output ' diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 3457d5db64..71cae2874d 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -460,7 +460,13 @@ test_expect_success 'submit --shelve' ' ) ' -# Update an existing shelved changelist +make_shelved_cl() { + test_commit "$1" >/dev/null && + git p4 submit --origin HEAD^ --shelve >/dev/null && + p4 -G changes -s shelved -m 1 | marshal_dump change +} + +# Update existing shelved changelists test_expect_success 'submit --update-shelve' ' test_when_finished cleanup_git && @@ -470,21 +476,19 @@ test_expect_success 'submit --update-shelve' ' p4 revert ... && cd "$git" && git config git-p4.skipSubmitEdit true && - test_commit "test-update-shelved-change" && - git p4 submit --origin=HEAD^ --shelve && + shelved_cl0=$(make_shelved_cl "shelved-change-0") && + echo shelved_cl0=$shelved_cl0 && + shelved_cl1=$(make_shelved_cl "shelved-change-1") && - shelf_cl=$(p4 -G changes -s shelved -m 1 |\ - marshal_dump change) && - test -n $shelf_cl && - echo "updating shelved change list $shelf_cl" && + echo "updating shelved change lists $shelved_cl0 and $shelved_cl1" && echo "updated-line" >>shelf.t && echo added-file.t >added-file.t && git add shelf.t added-file.t && - git rm -f test-update-shelved-change.t && + git rm -f shelved-change-1.t && git commit --amend -C HEAD && git show --stat HEAD && - git p4 submit -v --origin HEAD^ --update-shelve $shelf_cl && + git p4 submit -v --origin HEAD~2 --update-shelve $shelved_cl0 --update-shelve $shelved_cl1 && echo "done git p4 submit" ) && ( @@ -494,7 +498,7 @@ test_expect_success 'submit --update-shelve' ' p4 unshelve -c $change -s $change && grep -q updated-line shelf.t && p4 describe -S $change | grep added-file.t && - test_path_is_missing test-update-shelved-change.t + test_path_is_missing shelved-change-1.t ) ' diff --git a/t/test-lib.sh b/t/test-lib.sh index 116bd6a70c..9a0a21f49a 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -80,7 +80,7 @@ done,*) # from any previous runs. >"$GIT_TEST_TEE_OUTPUT_FILE" - (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; + (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" test "$(cat "$BASE.exit")" = 0 exit @@ -264,7 +264,6 @@ do shift ;; -x) trace=t - verbose=t shift ;; --verbose-log) verbose_log=t @@ -283,6 +282,11 @@ then test -z "$verbose_log" && verbose=t fi +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi + if test -n "$color" then # Save the color control sequences now rather than run tput @@ -586,7 +590,9 @@ maybe_setup_valgrind () { } want_trace () { - test "$trace" = t && test "$verbose" = t + test "$trace" = t && { + test "$verbose" = t || test "$verbose_log" = t + } } # This is a separate function because some tests use @@ -601,26 +607,40 @@ test_eval_inner_ () { } test_eval_ () { - # We run this block with stderr redirected to avoid extra cruft - # during a "-x" trace. Once in "set -x" mode, we cannot prevent + # If "-x" tracing is in effect, then we want to avoid polluting stderr + # with non-test commands. But once in "set -x" mode, we cannot prevent # the shell from printing the "set +x" to turn it off (nor the saving # of $? before that). But we can make sure that the output goes to # /dev/null. # - # The test itself is run with stderr put back to &4 (so either to - # /dev/null, or to the original stderr if --verbose was used). + # There are a few subtleties here: + # + # - we have to redirect descriptor 4 in addition to 2, to cover + # BASH_XTRACEFD + # + # - the actual eval has to come before the redirection block (since + # it needs to see descriptor 4 to set up its stderr) + # + # - likewise, any error message we print must be outside the block to + # access descriptor 4 + # + # - checking $? has to come immediately after the eval, but it must + # be _inside_ the block to avoid polluting the "set -x" output + # + + test_eval_inner_ "$@" </dev/null >&3 2>&4 { - test_eval_inner_ "$@" </dev/null >&3 2>&4 test_eval_ret_=$? if want_trace then set +x - if test "$test_eval_ret_" != 0 - then - say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" - fi fi - } 2>/dev/null + } 2>/dev/null 4>&2 + + if test "$test_eval_ret_" != 0 && want_trace + then + say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + fi return $test_eval_ret_ } @@ -1028,6 +1048,8 @@ test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PTHREADS" && test_set_prereq PTHREADS test -z "$NO_PYTHON" && test_set_prereq PYTHON test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE +test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1 +test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # Can we rely on git's output in the C locale? @@ -24,26 +24,14 @@ #include "cache.h" #include "quote.h" -/* - * "Normalize" a key argument by converting NULL to our trace_default, - * and otherwise passing through the value. All caller-facing functions - * should normalize their inputs in this way, though most get it - * for free by calling get_trace_fd() (directly or indirectly). - */ -static void normalize_trace_key(struct trace_key **key) -{ - static struct trace_key trace_default = { "GIT_TRACE" }; - if (!*key) - *key = &trace_default; -} +struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 }; +struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE); /* Get a trace file descriptor from "key" env variable. */ static int get_trace_fd(struct trace_key *key) { const char *trace; - normalize_trace_key(&key); - /* don't open twice */ if (key->initialized) return key->fd; @@ -81,8 +69,6 @@ static int get_trace_fd(struct trace_key *key) void trace_disable(struct trace_key *key) { - normalize_trace_key(&key); - if (key->need_close) close(key->fd); key->fd = 0; @@ -128,7 +114,6 @@ static int prepare_trace_line(const char *file, int line, static void trace_write(struct trace_key *key, const void *buf, unsigned len) { if (write_in_full(get_trace_fd(key), buf, len) < 0) { - normalize_trace_key(&key); warning("unable to write trace for %s: %s", key->key, strerror(errno)); trace_disable(key); @@ -167,13 +152,13 @@ static void trace_argv_vprintf_fl(const char *file, int line, { struct strbuf buf = STRBUF_INIT; - if (!prepare_trace_line(file, line, NULL, &buf)) + if (!prepare_trace_line(file, line, &trace_default_key, &buf)) return; strbuf_vaddf(&buf, format, ap); sq_quote_argv(&buf, argv, 0); - print_trace_line(NULL, &buf); + print_trace_line(&trace_default_key, &buf); } void trace_strbuf_fl(const char *file, int line, struct trace_key *key, @@ -188,8 +173,6 @@ void trace_strbuf_fl(const char *file, int line, struct trace_key *key, print_trace_line(key, &buf); } -static struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE); - static void trace_performance_vprintf_fl(const char *file, int line, uint64_t nanos, const char *format, va_list ap) @@ -215,7 +198,7 @@ void trace_printf(const char *format, ...) { va_list ap; va_start(ap, format); - trace_vprintf_fl(NULL, 0, NULL, format, ap); + trace_vprintf_fl(NULL, 0, &trace_default_key, format, ap); va_end(ap); } @@ -11,7 +11,10 @@ struct trace_key { unsigned int need_close : 1; }; +extern struct trace_key trace_default_key; + #define TRACE_KEY_INIT(name) { "GIT_TRACE_" #name, 0, 0, 0 } +extern struct trace_key trace_perf_key; extern void trace_repo_setup(const char *prefix); extern int trace_want(struct trace_key *key); @@ -77,24 +80,42 @@ extern void trace_performance_since(uint64_t start, const char *format, ...); * comma, but this is non-standard. */ -#define trace_printf(...) \ - trace_printf_key_fl(TRACE_CONTEXT, __LINE__, NULL, __VA_ARGS__) - -#define trace_printf_key(key, ...) \ - trace_printf_key_fl(TRACE_CONTEXT, __LINE__, key, __VA_ARGS__) - -#define trace_argv_printf(argv, ...) \ - trace_argv_printf_fl(TRACE_CONTEXT, __LINE__, argv, __VA_ARGS__) - -#define trace_strbuf(key, data) \ - trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data) - -#define trace_performance(nanos, ...) \ - trace_performance_fl(TRACE_CONTEXT, __LINE__, nanos, __VA_ARGS__) - -#define trace_performance_since(start, ...) \ - trace_performance_fl(TRACE_CONTEXT, __LINE__, getnanotime() - (start), \ - __VA_ARGS__) +#define trace_printf_key(key, ...) \ + do { \ + if (trace_pass_fl(key)) \ + trace_printf_key_fl(TRACE_CONTEXT, __LINE__, key, \ + __VA_ARGS__); \ + } while (0) + +#define trace_printf(...) trace_printf_key(&trace_default_key, __VA_ARGS__) + +#define trace_argv_printf(argv, ...) \ + do { \ + if (trace_pass_fl(&trace_default_key)) \ + trace_argv_printf_fl(TRACE_CONTEXT, __LINE__, \ + argv, __VA_ARGS__); \ + } while (0) + +#define trace_strbuf(key, data) \ + do { \ + if (trace_pass_fl(key)) \ + trace_strbuf_fl(TRACE_CONTEXT, __LINE__, key, data);\ + } while (0) + +#define trace_performance(nanos, ...) \ + do { \ + if (trace_pass_fl(&trace_perf_key)) \ + trace_performance_fl(TRACE_CONTEXT, __LINE__, nanos,\ + __VA_ARGS__); \ + } while (0) + +#define trace_performance_since(start, ...) \ + do { \ + if (trace_pass_fl(&trace_perf_key)) \ + trace_performance_fl(TRACE_CONTEXT, __LINE__, \ + getnanotime() - (start), \ + __VA_ARGS__); \ + } while (0) /* backend functions, use non-*fl macros instead */ __attribute__((format (printf, 4, 5))) @@ -108,6 +129,10 @@ extern void trace_strbuf_fl(const char *file, int line, struct trace_key *key, __attribute__((format (printf, 4, 5))) extern void trace_performance_fl(const char *file, int line, uint64_t nanos, const char *fmt, ...); +static inline int trace_pass_fl(struct trace_key *key) +{ + return key->fd || !key->initialized; +} #endif /* HAVE_VARIADIC_MACROS */ diff --git a/transport-helper.c b/transport-helper.c index bf05a2dcf1..5080150231 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -11,6 +11,7 @@ #include "sigchain.h" #include "argv-array.h" #include "refs.h" +#include "transport-internal.h" static int debug; @@ -650,7 +651,7 @@ static int fetch(struct transport *transport, if (process_connect(transport, 0)) { do_take_over(transport); - return transport->fetch(transport, nr_heads, to_fetch); + return transport->vtable->fetch(transport, nr_heads, to_fetch); } count = 0; @@ -882,7 +883,8 @@ static int push_refs_with_push(struct transport *transport, struct strbuf cas = STRBUF_INIT; strbuf_addf(&cas, "%s:%s", ref->name, oid_to_hex(&ref->old_oid_expect)); - string_list_append(&cas_options, strbuf_detach(&cas, NULL)); + string_list_append_nodup(&cas_options, + strbuf_detach(&cas, NULL)); } } if (buf.len == 0) { @@ -897,6 +899,7 @@ static int push_refs_with_push(struct transport *transport, strbuf_addch(&buf, '\n'); sendline(data, &buf); strbuf_release(&buf); + string_list_clear(&cas_options, 0); return push_update_refs_status(data, remote_refs, flags); } @@ -930,7 +933,8 @@ static int push_refs_with_export(struct transport *transport, private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); if (private && !get_oid(private, &oid)) { strbuf_addf(&buf, "^%s", private); - string_list_append(&revlist_args, strbuf_detach(&buf, NULL)); + string_list_append_nodup(&revlist_args, + strbuf_detach(&buf, NULL)); oidcpy(&ref->old_oid, &oid); } free(private); @@ -987,7 +991,7 @@ static int push_refs(struct transport *transport, if (process_connect(transport, 1)) { do_take_over(transport); - return transport->push_refs(transport, remote_refs, flags); + return transport->vtable->push_refs(transport, remote_refs, flags); } if (!remote_refs) { @@ -1035,7 +1039,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) if (process_connect(transport, for_push)) { do_take_over(transport); - return transport->get_refs_list(transport, for_push); + return transport->vtable->get_refs_list(transport, for_push); } if (data->push && for_push) @@ -1083,6 +1087,15 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) return ret; } +static struct transport_vtable vtable = { + set_helper_option, + get_refs_list, + fetch, + push_refs, + connect_helper, + release_helper +}; + int transport_helper_init(struct transport *transport, const char *name) { struct helper_data *data = xcalloc(1, sizeof(*data)); @@ -1094,12 +1107,7 @@ int transport_helper_init(struct transport *transport, const char *name) debug = 1; transport->data = data; - transport->set_option = set_helper_option; - transport->get_refs_list = get_refs_list; - transport->fetch = fetch; - transport->push_refs = push_refs; - transport->disconnect = release_helper; - transport->connect = connect_helper; + transport->vtable = &vtable; transport->smart_options = &(data->transport_options); return 0; } diff --git a/transport-internal.h b/transport-internal.h new file mode 100644 index 0000000000..3c1a29d727 --- /dev/null +++ b/transport-internal.h @@ -0,0 +1,61 @@ +#ifndef TRANSPORT_INTERNAL_H +#define TRANSPORT_INTERNAL_H + +struct ref; +struct transport; + +struct transport_vtable { + /** + * Returns 0 if successful, positive if the option is not + * recognized or is inapplicable, and negative if the option + * is applicable but the value is invalid. + **/ + int (*set_option)(struct transport *connection, const char *name, + const char *value); + /** + * Returns a list of the remote side's refs. In order to allow + * the transport to try to share connections, for_push is a + * hint as to whether the ultimate operation is a push or a fetch. + * + * If the transport is able to determine the remote hash for + * the ref without a huge amount of effort, it should store it + * in the ref's old_sha1 field; otherwise it should be all 0. + **/ + struct ref *(*get_refs_list)(struct transport *transport, int for_push); + + /** + * Fetch the objects for the given refs. Note that this gets + * an array, and should ignore the list structure. + * + * If the transport did not get hashes for refs in + * get_refs_list(), it should set the old_sha1 fields in the + * provided refs now. + **/ + int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); + + /** + * Push the objects and refs. Send the necessary objects, and + * then, for any refs where peer_ref is set and + * peer_ref->new_oid is different from old_oid, tell the + * remote side to update each ref in the list from old_oid to + * peer_ref->new_oid. + * + * Where possible, set the status for each ref appropriately. + * + * The transport must modify new_sha1 in the ref to the new + * value if the remote accepted the change. Note that this + * could be a different value from peer_ref->new_oid if the + * process involved generating new commits. + **/ + int (*push_refs)(struct transport *transport, struct ref *refs, int flags); + int (*connect)(struct transport *connection, const char *name, + const char *executable, int fd[2]); + + /** get_refs_list(), fetch(), and push_refs() can keep + * resources (such as a connection) reserved for further + * use. disconnect() releases these resources. + **/ + int (*disconnect)(struct transport *connection); +}; + +#endif diff --git a/transport.c b/transport.c index 7231d1b1b0..fc802260f6 100644 --- a/transport.c +++ b/transport.c @@ -17,6 +17,7 @@ #include "string-list.h" #include "sha1-array.h" #include "sigchain.h" +#include "transport-internal.h" static void set_upstreams(struct transport *transport, struct ref *refs, int pretend) @@ -607,6 +608,15 @@ static int disconnect_git(struct transport *transport) return 0; } +static struct transport_vtable taken_over_vtable = { + NULL, + get_refs_via_connect, + fetch_refs_via_pack, + git_transport_push, + NULL, + disconnect_git +}; + void transport_take_over(struct transport *transport, struct child_process *child) { @@ -624,12 +634,7 @@ void transport_take_over(struct transport *transport, data->got_remote_heads = 0; transport->data = data; - transport->set_option = NULL; - transport->get_refs_list = get_refs_via_connect; - transport->fetch = fetch_refs_via_pack; - transport->push = NULL; - transport->push_refs = git_transport_push; - transport->disconnect = disconnect_git; + transport->vtable = &taken_over_vtable; transport->smart_options = &(data->options); transport->cannot_reuse = 1; @@ -752,6 +757,24 @@ void transport_check_allowed(const char *type) die("transport '%s' not allowed", type); } +static struct transport_vtable bundle_vtable = { + NULL, + get_refs_from_bundle, + fetch_refs_from_bundle, + NULL, + NULL, + close_bundle +}; + +static struct transport_vtable builtin_smart_vtable = { + NULL, + get_refs_via_connect, + fetch_refs_via_pack, + git_transport_push, + connect_git, + disconnect_git +}; + struct transport *transport_get(struct remote *remote, const char *url) { const char *helper; @@ -788,9 +811,7 @@ struct transport *transport_get(struct remote *remote, const char *url) struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); transport_check_allowed("file"); ret->data = data; - ret->get_refs_list = get_refs_from_bundle; - ret->fetch = fetch_refs_from_bundle; - ret->disconnect = close_bundle; + ret->vtable = &bundle_vtable; ret->smart_options = NULL; } else if (!is_url(url) || starts_with(url, "file://") @@ -805,12 +826,7 @@ struct transport *transport_get(struct remote *remote, const char *url) */ struct git_transport_data *data = xcalloc(1, sizeof(*data)); ret->data = data; - ret->set_option = NULL; - ret->get_refs_list = get_refs_via_connect; - ret->fetch = fetch_refs_via_pack; - ret->push_refs = git_transport_push; - ret->connect = connect_git; - ret->disconnect = disconnect_git; + ret->vtable = &builtin_smart_vtable; ret->smart_options = &(data->options); data->conn = NULL; @@ -844,9 +860,9 @@ int transport_set_option(struct transport *transport, git_reports = set_git_option(transport->smart_options, name, value); - if (transport->set_option) - protocol_reports = transport->set_option(transport, name, - value); + if (transport->vtable->set_option) + protocol_reports = transport->vtable->set_option(transport, + name, value); /* If either report is 0, report 0 (success). */ if (!git_reports || !protocol_reports) @@ -969,13 +985,7 @@ int transport_push(struct transport *transport, *reject_reasons = 0; transport_verify_remote_names(refspec_nr, refspec); - if (transport->push) { - /* Maybe FIXME. But no important transport uses this case. */ - if (flags & TRANSPORT_PUSH_SET_UPSTREAM) - die("This transport does not support using --set-upstream"); - - return transport->push(transport, refspec_nr, refspec, flags); - } else if (transport->push_refs) { + if (transport->vtable->push_refs) { struct ref *remote_refs; struct ref *local_refs = get_local_heads(); int match_flags = MATCH_REFS_NONE; @@ -988,7 +998,7 @@ int transport_push(struct transport *transport, if (check_push_refs(local_refs, refspec_nr, refspec) < 0) return -1; - remote_refs = transport->get_refs_list(transport, 1); + remote_refs = transport->vtable->get_refs_list(transport, 1); if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -1063,7 +1073,7 @@ int transport_push(struct transport *transport, } if (!(flags & TRANSPORT_RECURSE_SUBMODULES_ONLY)) - push_ret = transport->push_refs(transport, remote_refs, flags); + push_ret = transport->vtable->push_refs(transport, remote_refs, flags); else push_ret = 0; err = push_had_errors(remote_refs); @@ -1097,7 +1107,7 @@ int transport_push(struct transport *transport, const struct ref *transport_get_remote_refs(struct transport *transport) { if (!transport->got_remote_refs) { - transport->remote_refs = transport->get_refs_list(transport, 0); + transport->remote_refs = transport->vtable->get_refs_list(transport, 0); transport->got_remote_refs = 1; } @@ -1134,7 +1144,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs) heads[nr_heads++] = rm; } - rc = transport->fetch(transport, nr_heads, heads); + rc = transport->vtable->fetch(transport, nr_heads, heads); free(heads); return rc; @@ -1151,8 +1161,8 @@ void transport_unlock_pack(struct transport *transport) int transport_connect(struct transport *transport, const char *name, const char *exec, int fd[2]) { - if (transport->connect) - return transport->connect(transport, name, exec, fd); + if (transport->vtable->connect) + return transport->vtable->connect(transport, name, exec, fd); else die("Operation not supported by protocol"); } @@ -1160,8 +1170,8 @@ int transport_connect(struct transport *transport, const char *name, int transport_disconnect(struct transport *transport) { int ret = 0; - if (transport->disconnect) - ret = transport->disconnect(transport); + if (transport->vtable->disconnect) + ret = transport->vtable->disconnect(transport); free(transport); return ret; } diff --git a/transport.h b/transport.h index bc5571574b..731c78b679 100644 --- a/transport.h +++ b/transport.h @@ -30,6 +30,8 @@ enum transport_family { }; struct transport { + const struct transport_vtable *vtable; + struct remote *remote; const char *url; void *data; @@ -59,59 +61,6 @@ struct transport { */ const struct string_list *push_options; - /** - * Returns 0 if successful, positive if the option is not - * recognized or is inapplicable, and negative if the option - * is applicable but the value is invalid. - **/ - int (*set_option)(struct transport *connection, const char *name, - const char *value); - - /** - * Returns a list of the remote side's refs. In order to allow - * the transport to try to share connections, for_push is a - * hint as to whether the ultimate operation is a push or a fetch. - * - * If the transport is able to determine the remote hash for - * the ref without a huge amount of effort, it should store it - * in the ref's old_sha1 field; otherwise it should be all 0. - **/ - struct ref *(*get_refs_list)(struct transport *transport, int for_push); - - /** - * Fetch the objects for the given refs. Note that this gets - * an array, and should ignore the list structure. - * - * If the transport did not get hashes for refs in - * get_refs_list(), it should set the old_sha1 fields in the - * provided refs now. - **/ - int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs); - - /** - * Push the objects and refs. Send the necessary objects, and - * then, for any refs where peer_ref is set and - * peer_ref->new_oid is different from old_oid, tell the - * remote side to update each ref in the list from old_oid to - * peer_ref->new_oid. - * - * Where possible, set the status for each ref appropriately. - * - * The transport must modify new_sha1 in the ref to the new - * value if the remote accepted the change. Note that this - * could be a different value from peer_ref->new_oid if the - * process involved generating new commits. - **/ - int (*push_refs)(struct transport *transport, struct ref *refs, int flags); - int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); - int (*connect)(struct transport *connection, const char *name, - const char *executable, int fd[2]); - - /** get_refs_list(), fetch(), and push_refs() can keep - * resources (such as a connection) reserved for further - * use. disconnect() releases these resources. - **/ - int (*disconnect)(struct transport *connection); char *pack_lockfile; signed verbose : 3; /** diff --git a/tree-walk.c b/tree-walk.c index 684f0e3373..63a87ed666 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -1011,7 +1011,8 @@ static enum interesting do_match(const struct name_entry *entry, * character. More accurate matching can then * be performed in the submodule itself. */ - if (ps->recursive && S_ISGITLINK(entry->mode) && + if (ps->recurse_submodules && + S_ISGITLINK(entry->mode) && !ps_strncmp(item, match + baselen, entry->path, item->nowildcard_len - baselen)) @@ -1060,7 +1061,7 @@ match_wildcards: * character. More accurate matching can then * be performed in the submodule itself. */ - if (ps->recursive && S_ISGITLINK(entry->mode) && + if (ps->recurse_submodules && S_ISGITLINK(entry->mode) && !ps_strncmp(item, match, base->buf + base_offset, item->nowildcard_len)) { strbuf_setlen(base, base_offset + baselen); diff --git a/upload-pack.c b/upload-pack.c index 6d5f3c0d39..d5de18127c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -18,6 +18,7 @@ #include "parse-options.h" #include "argv-array.h" #include "prio-queue.h" +#include "protocol.h" static const char * const upload_pack_usage[] = { N_("git upload-pack [<options>] <dir>"), @@ -1066,6 +1067,23 @@ int cmd_main(int argc, const char **argv) die("'%s' does not appear to be a git repository", dir); git_config(upload_pack_config, NULL); - upload_pack(); + + switch (determine_protocol_version_server()) { + case protocol_v1: + /* + * v1 is just the original protocol with a version string, + * so just fall through after writing the version string. + */ + if (advertise_refs || !stateless_rpc) + packet_write_fmt(1, "version 1\n"); + + /* fallthrough */ + case protocol_v0: + upload_pack(); + break; + case protocol_unknown_version: + BUG("unknown protocol version"); + } + return 0; } @@ -3,6 +3,7 @@ #include "strbuf.h" const char git_version_string[] = GIT_VERSION; +const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT; const char *git_user_agent(void) { @@ -2,6 +2,7 @@ #define VERSION_H extern const char git_version_string[]; +extern const char git_built_from_commit_string[]; const char *git_user_agent(void); const char *git_user_agent_sanitized(void); diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 785ecb0899..c1937a2911 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -27,22 +27,28 @@ extern "C" { #endif /* #ifdef __cplusplus */ +/* xpparm_t.flags */ +#define XDF_NEED_MINIMAL (1 << 0) + +#define XDF_IGNORE_WHITESPACE (1 << 1) +#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2) +#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3) +#define XDF_IGNORE_CR_AT_EOL (1 << 4) +#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \ + XDF_IGNORE_WHITESPACE_CHANGE | \ + XDF_IGNORE_WHITESPACE_AT_EOL | \ + XDF_IGNORE_CR_AT_EOL) -#define XDF_NEED_MINIMAL (1 << 1) -#define XDF_IGNORE_WHITESPACE (1 << 2) -#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) -#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) -#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) +#define XDF_IGNORE_BLANK_LINES (1 << 7) -#define XDF_PATIENCE_DIFF (1 << 5) -#define XDF_HISTOGRAM_DIFF (1 << 6) +#define XDF_PATIENCE_DIFF (1 << 14) +#define XDF_HISTOGRAM_DIFF (1 << 15) #define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) -#define XDF_IGNORE_BLANK_LINES (1 << 7) - -#define XDF_INDENT_HEURISTIC (1 << 8) +#define XDF_INDENT_HEURISTIC (1 << 23) +/* xdemitconf_t.flags */ #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) @@ -80,6 +86,10 @@ typedef struct s_mmbuffer { typedef struct s_xpparam { unsigned long flags; + + /* See Documentation/diff-options.txt. */ + char **anchors; + size_t anchors_nr; } xpparam_t; typedef struct s_xdemitcb { diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 6881445e4a..7778dc2b19 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -121,6 +121,12 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv); } +static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) +{ + char dummy[1]; + return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0; +} + struct func_line { long len; char buf[80]; @@ -178,7 +184,6 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, /* Appended chunk? */ if (i1 >= xe->xdf1.nrec) { - char dummy[1]; long i2 = xch->i2; /* @@ -186,8 +191,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, * a whole function was added. */ while (i2 < xe->xdf2.nrec) { - if (match_func_rec(&xe->xdf2, xecfg, i2, - dummy, sizeof(dummy)) >= 0) + if (is_func_rec(&xe->xdf2, xecfg, i2)) goto post_context_calculation; i2++; } @@ -200,6 +204,9 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, } fs1 = get_func_line(xe, xecfg, NULL, i1, -1); + while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) && + !is_func_rec(&xe->xdf1, xecfg, fs1 - 1)) + fs1--; if (fs1 < 0) fs1 = 0; if (fs1 < s1) { diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c index a44e776328..f3573d9f00 100644 --- a/xdiff/xpatience.c +++ b/xdiff/xpatience.c @@ -62,6 +62,12 @@ struct hashmap { * initially, "next" reflects only the order in file1. */ struct entry *next, *previous; + + /* + * If 1, this entry can serve as an anchor. See + * Documentation/diff-options.txt for more information. + */ + unsigned anchor : 1; } *entries, *first, *last; /* were common records found? */ unsigned long has_matches; @@ -70,8 +76,19 @@ struct hashmap { xpparam_t const *xpp; }; +static int is_anchor(xpparam_t const *xpp, const char *line) +{ + int i; + for (i = 0; i < xpp->anchors_nr; i++) { + if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i]))) + return 1; + } + return 0; +} + /* The argument "pass" is 1 for the first file, 2 for the second. */ -static void insert_record(int line, struct hashmap *map, int pass) +static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, + int pass) { xrecord_t **records = pass == 1 ? map->env->xdf1.recs : map->env->xdf2.recs; @@ -110,6 +127,7 @@ static void insert_record(int line, struct hashmap *map, int pass) return; map->entries[index].line1 = line; map->entries[index].hash = record->ha; + map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr); if (!map->first) map->first = map->entries + index; if (map->last) { @@ -147,11 +165,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, /* First, fill with entries from the first file */ while (count1--) - insert_record(line1++, result, 1); + insert_record(xpp, line1++, result, 1); /* Then search for matches in the second file */ while (count2--) - insert_record(line2++, result, 2); + insert_record(xpp, line2++, result, 2); return 0; } @@ -192,14 +210,28 @@ static struct entry *find_longest_common_sequence(struct hashmap *map) int longest = 0, i; struct entry *entry; + /* + * If not -1, this entry in sequence must never be overridden. + * Therefore, overriding entries before this has no effect, so + * do not do that either. + */ + int anchor_i = -1; + for (entry = map->first; entry; entry = entry->next) { if (!entry->line2 || entry->line2 == NON_UNIQUE) continue; i = binary_search(sequence, longest, entry); entry->previous = i < 0 ? NULL : sequence[i]; - sequence[++i] = entry; - if (i == longest) + ++i; + if (i <= anchor_i) + continue; + sequence[i] = entry; + if (entry->anchor) { + anchor_i = i; + longest = anchor_i + 1; + } else if (i == longest) { longest++; + } } /* No common unique lines were found */ diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 088001db4f..88e5995535 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -156,6 +156,24 @@ int xdl_blankline(const char *line, long size, long flags) return (i == size); } +/* + * Have we eaten everything on the line, except for an optional + * CR at the very end? + */ +static int ends_with_optional_cr(const char *l, long s, long i) +{ + int complete = s && l[s-1] == '\n'; + + if (complete) + s--; + if (s == i) + return 1; + /* do not ignore CR at the end of an incomplete line */ + if (complete && s == i + 1 && l[i] == '\r') + return 1; + return 0; +} + int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) { int i1, i2; @@ -170,7 +188,8 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) /* * -w matches everything that matches with -b, and -b in turn - * matches everything that matches with --ignore-space-at-eol. + * matches everything that matches with --ignore-space-at-eol, + * which in turn matches everything that matches with --ignore-cr-at-eol. * * Each flavor of ignoring needs different logic to skip whitespaces * while we have both sides to compare. @@ -204,6 +223,14 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) i1++; i2++; } + } else if (flags & XDF_IGNORE_CR_AT_EOL) { + /* Find the first difference and see how the line ends */ + while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) { + i1++; + i2++; + } + return (ends_with_optional_cr(l1, s1, i1) && + ends_with_optional_cr(l2, s2, i2)); } /* @@ -230,9 +257,16 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; + int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; for (; ptr < top && *ptr != '\n'; ptr++) { - if (XDL_ISSPACE(*ptr)) { + if (cr_at_eol_only) { + /* do not ignore CR at the end of an incomplete line */ + if (*ptr == '\r' && + (ptr + 1 < top && ptr[1] == '\n')) + continue; + } + else if (XDL_ISSPACE(*ptr)) { const char *ptr2 = ptr; int at_eol; while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) |
