diff options
149 files changed, 2702 insertions, 1143 deletions
diff --git a/.travis.yml b/.travis.yml index 1fdcec8437..adab5b89bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,6 +35,21 @@ env: # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X - GIT_SKIP_TESTS="t9810 t9816" +matrix: + include: + - env: Documentation + os: linux + compiler: clang + addons: + apt: + packages: + - asciidoc + - xmlto + before_install: + before_script: + script: ci/test-documentation.sh + after_failure: + before_install: - > case "${TRAVIS_OS_NAME:-linux}" in diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt index af184783bc..fedd9968e5 100644 --- a/Documentation/RelNotes/2.8.3.txt +++ b/Documentation/RelNotes/2.8.3.txt @@ -67,4 +67,35 @@ Fixes since v2.8.2 recurses into, but this was incorrect when the command was not run from the root level of the superproject. + * The test scripts for "git p4" (but not "git p4" implementation + itself) has been updated so that they would work even on a system + where the installed version of Python is python 3. + + * The "user.useConfigOnly" configuration variable makes it an error + if users do not explicitly set user.name and user.email. However, + its check was not done early enough and allowed another error to + trigger, reporting that the default value we guessed from the + system setting was unusable. This was a suboptimal end-user + experience as we want the users to set user.name/user.email without + relying on the auto-detection at all. + + * "git mv old new" did not adjust the path for a submodule that lives + as a subdirectory inside old/ directory correctly. + + * "git push" from a corrupt repository that attempts to push a large + number of refs deadlocked; the thread to relay rejection notices + for these ref updates blocked on writing them to the main thread, + after the main thread at the receiving end notices that the push + failed and decides not to read these notices and return a failure. + + * A question by "git send-email" to ask the identity of the sender + has been updated. + + * Recent update to Git LFS broke "git p4" by changing the output from + its "lfs pointer" subcommand. + + * Some multi-byte encoding can have a backslash byte as a later part + of one letter, which would confuse "highlight" filter used in + gitweb. + Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.9.0.txt b/Documentation/RelNotes/2.9.0.txt index 70fa66c8f1..211b9722dc 100644 --- a/Documentation/RelNotes/2.9.0.txt +++ b/Documentation/RelNotes/2.9.0.txt @@ -16,12 +16,20 @@ The output formats of "git log" that indents the commit log message by 4 spaces now expands HT in the log message by default. You can use the "--no-expand-tabs" option to disable this. +"git commit-tree" plumbing command required the user to always sign +its result when the user sets the commit.gpgsign configuration +variable, which was an ancient mistake, which this release corrects. +A script that drives commit-tree, if it relies on this mistake, now +needs to read commit.gpgsign and pass the -S option as necessary. + Updates since v2.8 ------------------ UI, Workflows & Features + * Comes with git-multimail 1.3.1 (in contrib/). + * The end-user facing Porcelain level commands like "diff" and "log" now enables the rename detection by default. @@ -116,6 +124,14 @@ UI, Workflows & Features more readable by using a blank line as a strong hint that the contents before and after it belong to a logically separate unit. + * A new configuration variable core.hooksPath allows customizing + where the hook directory is. + + * An earlier addition of "sanitize_submodule_env" with 14111fc4 (git: + submodule honor -c credential.* from command line, 2016-02-29) + turned out to be a convoluted no-op; implement what it wanted to do + correctly, and stop filtering settings given via "git -c var=val". + Performance, Internal Implementation, Development Support etc. @@ -157,12 +173,6 @@ Performance, Internal Implementation, Development Support etc. have been moved to t/helper/ subdirectory to reduce clutter at the top level of the tree. - Note that this can break your tests if you check out revisions - across the merge boundary of this topic, e0b58519 (Merge branch - 'nd/test-helpers', 2016-04-29), as bin-wrappers/test-* are not - rebuilt to point the underlying executables. For now, "make - distclean" is your friend. - * Unify internal logic between "git tag -v" and "git verify-tag" commands by making one directly call into the other. (merge bef234b st/verify-tag later to maint). @@ -180,6 +190,21 @@ Performance, Internal Implementation, Development Support etc. * Move from unsigned char[20] to struct object_id continues. + * Update of "git submodule" to move pieces of logic to C continues. + + * The code for warning_errno/die_errno has been refactored and a new + error_errno() reporting helper is introduced. + (merge 1da045f nd/error-errno later to maint). + + * Running tests with '-x' option to trace the individual command + executions is a useful way to debug test scripts, but some tests + that capture the standard error stream and check what the command + said can be broken with the trace output mixed in. When running + our tests under "bash", however, we can redirect the trace output + to another file descriptor to keep the standard error of programs + being tested intact. + (merge d88785e jk/test-send-sh-x-trace-elsewhere later to maint). + Also contains various documentation updates and code clean-ups. @@ -360,6 +385,63 @@ notes for details). gitweb. (merge 029f372 sk/gitweb-highlight-encoding later to maint). + * "git commit-tree" plumbing command required the user to always sign + its result when the user sets the commit.gpgsign configuration + variable, which was an ancient mistake. Rework "git rebase" that + relied on this mistake so that it reads commit.gpgsign and pass (or + not pass) the -S option to "git commit-tree" to keep the end-user + expectation the same, while teaching "git commit-tree" to ignore + the configuration variable. This will stop requiring the users to + sign commit objects used internally as an implementation detail of + "git stash". + (merge 6694856 jc/commit-tree-ignore-commit-gpgsign later to maint). + + * "http.cookieFile" configuration variable clearly wants a pathname, + but we forgot to treat it as such by e.g. applying tilde expansion. + (merge e5a39ad bn/http-cookiefile-config later to maint). + + * Consolidate description of tilde-expansion that is done to + configuration variables that take pathname to a single place. + (merge dca83ab jc/config-pathname-type later to maint). + + * Correct faulty recommendation to use "git submodule deinit ." when + de-initialising all submodules, which would result in a strange + error message in a pathological corner case. + (merge f6a5279 sb/submodule-deinit-all later to maint). + + * Many 'linkgit:<git documentation page>' references were broken, + which are all fixed with this. + (merge 1cca17d jc/linkgit-fix later to maint). + + * "git rerere" can get confused by conflict markers deliberately left + by the inner merge step, because they are indistinguishable from + the real conflict markers left by the outermost merge which are + what the end user and "rerere" need to look at. This was fixed by + making the conflict markers left by the inner merges a bit longer. + (merge 0f9fd5c jc/ll-merge-internal later to maint). + + * CI test was taught to build documentation pages. + (merge b98712b ls/travis-build-doc later to maint). + + * "git fsck" learned to catch NUL byte in a commit object as + potential error and warn. + (merge 6d2d780 jc/fsck-nul-in-commit later to maint). + + * Portability enhancement for "rebase -i" to help platforms whose + shell does not like "for i in <empty>" (which is not POSIX-kosher). + (merge 8e98b35 jk/rebase-interative-eval-fix later to maint). + + * On Windows, .git and optionally any files whose name starts with a + dot are now marked as hidden, with a core.hideDotFiles knob to + customize this behaviour. + (merge ebf31e7 js/windows-dotgit later to maint). + + * Documentation for "git merge --verify-signatures" has been updated + to clarify that the signature of only the commit at the tip is + verified. Also the phrasing used for signature and key validity is + adjusted to align with that used by OpenPGP. + (merge 05a5869 kf/gpg-sig-verification-doc later to maint). + * Other minor clean-ups and documentation updates (merge 8b5a3e9 kn/for-each-tag-branch later to maint). (merge 99dab16 sb/misc-cleanups later to maint). @@ -373,3 +455,13 @@ notes for details). (merge fa72245 ew/normal-to-e later to maint). (merge 2e39a24 rn/glossary-typofix later to maint). (merge cadfbef sb/clean-test-fix later to maint). + (merge 832c0e5 lp/typofixes later to maint). + (merge f5ee54a sb/z-is-gnutar-ism later to maint). + (merge 2e3926b va/i18n-misc-updates later to maint). + (merge f212dcc bn/config-doc-tt-varnames later to maint). + (merge f54bea4 nd/remote-plural-ours-plus-theirs later to maint). + (merge 2bb0518 ak/t4151-ls-files-could-be-empty later to maint). + (merge 4df4313 jc/test-seq later to maint). + (merge a75a308 tb/t5601-sed-fix later to maint). + (merge 6c1fbe1 va/i18n-remote-comment-to-align later to maint). + (merge dee2303 va/mailinfo-doc-typofix later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index ece0acdbab..53f00dbc26 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -81,13 +81,16 @@ Includes You can include one config file from another by setting the special `include.path` variable to the name of the file to be included. The +variable takes a pathname as its value, and is subject to tilde +expansion. + +The included file is expanded immediately, as if its contents had been found at the location of the include directive. If the value of the `include.path` variable is a relative path, the path is considered to be relative to the configuration file in which the include directive was -found. The value of `include.path` is subject to tilde expansion: `~/` -is expanded to the value of `$HOME`, and `~user/` to the specified -user's home directory. See below for examples. +found. See below for examples. + Example ~~~~~~~ @@ -114,7 +117,7 @@ Example [include] path = /path/to/foo.inc ; include by absolute path path = foo ; expand "foo" relative to the current file - path = ~/foo ; expand "foo" in your $HOME directory + path = ~/foo ; expand "foo" in your `$HOME` directory Values @@ -169,6 +172,13 @@ thing on the same output line (e.g. opening parenthesis before the list of branch names in `log --decorate` output) is set to be painted with `bold` or some other attribute. +pathname:: + A variable that takes a pathname value can be given a + string that begins with "`~/`" or "`~user/`", and the usual + tilde expansion happens to such a string: `~/` + is expanded to the value of `$HOME`, and `~user/` to the + specified user's home directory. + Variables ~~~~~~~~~ @@ -269,6 +279,12 @@ See linkgit:git-update-index[1]. + The default is true (when core.filemode is not specified in the config file). +core.hideDotFiles:: + (Windows-only) If true, mark newly-created directories and files whose + name starts with a dot as hidden. If 'dotGitOnly', only the `.git/` + directory is hidden, but no other files starting with a dot. The + default mode is 'dotGitOnly'. + core.ignoreCase:: If true, this option enables various workarounds to enable Git to work better on filesystems that are not case sensitive, @@ -337,9 +353,9 @@ core.quotePath:: core.eol:: Sets the line ending type to use in the working directory for - files that have the `text` property set. Alternatives are - 'lf', 'crlf' and 'native', which uses the platform's native - line ending. The default value is `native`. See + files that have the `text` property set when core.autocrlf is false. + Alternatives are 'lf', 'crlf' and 'native', which uses the platform's + native line ending. The default value is `native`. See linkgit:gitattributes[5] for more information on end-of-line conversion. @@ -486,10 +502,10 @@ repository's usual working tree). core.logAllRefUpdates:: Enable the reflog. Updates to a ref <ref> is logged to the file - "$GIT_DIR/logs/<ref>", by appending the new and old + "`$GIT_DIR/logs/<ref>`", by appending the new and old SHA-1, the date/time and the reason of the update, but only when the file exists. If this configuration - variable is set to true, missing "$GIT_DIR/logs/<ref>" + variable is set to true, missing "`$GIT_DIR/logs/<ref>`" file is automatically created for branch heads (i.e. under refs/heads/), remote refs (i.e. under refs/remotes/), note refs (i.e. under refs/notes/), and the symbolic ref HEAD. @@ -593,12 +609,11 @@ be delta compressed, but larger binary media files won't be. Common unit suffixes of 'k', 'm', or 'g' are supported. core.excludesFile:: - In addition to '.gitignore' (per-directory) and - '.git/info/exclude', Git looks into this file for patterns - of files which are not meant to be tracked. "`~/`" is expanded - to the value of `$HOME` and "`~user/`" to the specified user's - home directory. Its default value is $XDG_CONFIG_HOME/git/ignore. - If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore + Specifies the pathname to the file that contains patterns to + describe paths that are not meant to be tracked, in addition + to '.gitignore' (per-directory) and '.git/info/exclude'. + Defaults to `$XDG_CONFIG_HOME/git/ignore`. + If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore` is used instead. See linkgit:gitignore[5]. core.askPass:: @@ -615,8 +630,25 @@ core.attributesFile:: '.git/info/attributes', Git looks into this file for attributes (see linkgit:gitattributes[5]). Path expansions are made the same way as for `core.excludesFile`. Its default value is - $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not - set or empty, $HOME/.config/git/attributes is used instead. + `$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not + set or empty, `$HOME/.config/git/attributes` is used instead. + +core.hooksPath:: + By default Git will look for your hooks in the + '$GIT_DIR/hooks' directory. Set this to different path, + e.g. '/etc/git/hooks', and Git will try to find your hooks in + that directory, e.g. '/etc/git/hooks/pre-receive' instead of + in '$GIT_DIR/hooks/pre-receive'. ++ +The path can be either absolute or relative. A relative path is +taken as relative to the directory where the hooks are run (see +the "DESCRIPTION" section of linkgit:githooks[5]). ++ +This configuration variable is useful in cases where you'd like to +centrally configure your Git hooks instead of configuring them on a +per-repository basis, or as a more flexible and centralized +alternative to having an `init.templateDir` where you've changed +default hooks. core.editor:: Commands such as `commit` and `tag` that lets you edit @@ -1106,9 +1138,12 @@ commit.status:: message. Defaults to true. commit.template:: - Specify a file to use as the template for new commit messages. - "`~/`" is expanded to the value of `$HOME` and "`~user/`" to the - specified user's home directory. + Specify the pathname of a file to use as the template for + new commit messages. + +commit.verbose:: + A boolean or int to specify the level of verbose with `git commit`. + See linkgit:git-commit[1]. credential.helper:: Specify an external helper to be called when a username or @@ -1259,6 +1294,10 @@ format.outputDirectory:: Set a custom directory to store the resulting files instead of the current working directory. +format.useAutoBase:: + A boolean value which lets you enable the `--base=auto` option of + format-patch by default. + filter.<driver>.clean:: The command which is used to convert the content of a worktree file to a blob upon checkin. See linkgit:gitattributes[5] for @@ -1335,7 +1374,7 @@ gc.worktreePruneExpire:: 'git worktree prune --expire 3.months.ago'. This config variable can be used to set a different grace period. The value "now" may be used to disable the grace - period and prune $GIT_DIR/worktrees immediately, or "never" + period and prune `$GIT_DIR/worktrees` immediately, or "never" may be used to suppress pruning. gc.reflogExpire:: @@ -1475,13 +1514,13 @@ grep.fallbackToNoIndex:: is executed outside of a git repository. Defaults to false. gpg.program:: - Use this custom program instead of "gpg" found on $PATH when + Use this custom program instead of "`gpg`" found on `$PATH` when making or verifying a PGP signature. The program must support the same command-line interface as GPG, namely, to verify a detached - signature, "gpg --verify $file - <$signature" is run, and the + signature, "`gpg --verify $file - <$signature`" is run, and the program is expected to signal a good signature by exiting with code 0, and to generate an ASCII-armored detached signature, the - standard input of "gpg -bsau $key" is fed with the contents to be + standard input of "`gpg -bsau $key`" is fed with the contents to be signed, and the program is expected to send the result to its standard output. @@ -1494,7 +1533,7 @@ gui.diffContext:: made by the linkgit:git-gui[1]. The default is "5". gui.displayUntracked:: - Determines if linkgit::git-gui[1] shows untracked files + Determines if linkgit:git-gui[1] shows untracked files in the file list. The default is "true". gui.encoding:: @@ -1662,11 +1701,12 @@ http.extraHeader:: config, an empty value will reset the extra headers to the empty list. http.cookieFile:: - File containing previously stored cookie lines which should be used + The pathname of a file containing previously stored cookie lines, + which should be used in the Git http session, if they match the server. The file format of the file to read cookies from should be plain HTTP headers or - the Netscape/Mozilla cookie file format (see linkgit:curl[1]). - NOTE that the file specified with http.cookieFile is only used as + the Netscape/Mozilla cookie file format (see `curl(1)`). + NOTE that the file specified with http.cookieFile is used only as input unless http.saveCookies is set. http.saveCookies:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 4b0318e2ac..3cb301556e 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -271,7 +271,7 @@ For example, `--word-diff-regex=.` will treat each character as a word and, correspondingly, show differences character by character. + The regex can also be set via a diff driver or configuration option, see -linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly +linkgit:gitattributes[5] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers override configuration settings. diff --git a/Documentation/everyday.txto b/Documentation/everyday.txto index c5047d8f9b..ae555bd47e 100644 --- a/Documentation/everyday.txto +++ b/Documentation/everyday.txto @@ -1,7 +1,7 @@ Everyday Git With 20 Commands Or So =================================== -This document has been moved to linkgit:giteveryday[1]. +This document has been moved to linkgit:giteveryday[7]. Please let the owners of the referring site know so that they can update the link you clicked to get here. diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index e94367a5ed..611754f10b 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -112,7 +112,7 @@ EXIT STATUS SEE ALSO -------- linkgit:gitignore[5] -linkgit:gitconfig[5] +linkgit:git-config[1] linkgit:git-ls-files[1] GIT diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 48c33d7ed7..cb69faab68 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -61,8 +61,8 @@ OPTIONS stuck to the option without a space. --no-gpg-sign:: - Countermand `commit.gpgSign` configuration variable that is - set to force each and every commit to be signed. + Do not GPG-sign commit, to countermand a `--gpg-sign` option + given earlier on the command line. Commit Information diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 9ec6b3cc17..d474226eb7 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -290,7 +290,8 @@ configuration variable documented in linkgit:git-config[1]. what changes the commit has. Note that this diff output doesn't have its lines prefixed with '#'. This diff will not be a part - of the commit message. + of the commit message. See the `commit.verbose` configuration + variable in linkgit:git-config[1]. + If specified twice, show in addition the unified diff between what would be committed and the worktree files, i.e. the unstaged diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 73fd9e8230..003731f6a9 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -205,7 +205,7 @@ to other tags will be rewritten to point to the underlying commit. Remap to ancestor ~~~~~~~~~~~~~~~~~ -By using linkgit:rev-list[1] arguments, e.g., path limiters, you can limit the +By using linkgit:git-rev-list[1] arguments, e.g., path limiters, you can limit the set of revisions which get rewritten. However, positive refs on the command line are distinguished: we don't let them be excluded by such limiters. For this purpose, they are instead rewritten to point at the nearest ancestor that diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index c52578bb87..d9d406dcfb 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -179,7 +179,7 @@ returns an empty string instead. As a special case for the date-type fields, you may specify a format for the date by adding `:` followed by date format name (see the -values the `--date` option to linkgit::git-rev-list[1] takes). +values the `--date` option to linkgit:git-rev-list[1] takes). EXAMPLES diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 6821441d7d..bdeecd59e0 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -265,6 +265,11 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`. Output an all-zero hash in each patch's From header instead of the hash of the commit. +--base=<commit>:: + Record the base tree information to identify the state the + patch series applies to. See the BASE TREE INFORMATION section + below for details. + --root:: Treat the revision argument as a <revision range>, even if it is just a single commit (that would normally be treated as a @@ -520,6 +525,61 @@ This should help you to submit patches inline using KMail. 5. Back in the compose window: add whatever other text you wish to the message, complete the addressing and subject fields, and press send. +BASE TREE INFORMATION +--------------------- + +The base tree information block is used for maintainers or third party +testers to know the exact state the patch series applies to. It consists +of the 'base commit', which is a well-known commit that is part of the +stable part of the project history everybody else works off of, and zero +or more 'prerequisite patches', which are well-known patches in flight +that is not yet part of the 'base commit' that need to be applied on top +of 'base commit' in topological order before the patches can be applied. + +The 'base commit' is shown as "base-commit: " followed by the 40-hex of +the commit object name. A 'prerequisite patch' is shown as +"prerequisite-patch-id: " followed by the 40-hex 'patch id', which can +be obtained by passing the patch through the `git patch-id --stable` +command. + +Imagine that on top of the public commit P, you applied well-known +patches X, Y and Z from somebody else, and then built your three-patch +series A, B, C, the history would be like: + +................................................ +---P---X---Y---Z---A---B---C +................................................ + +With `git format-patch --base=P -3 C` (or variants thereof, e.g. with +`--cover-letter` of using `Z..C` instead of `-3 C` to specify the +range), the base tree information block is shown at the end of the +first message the command outputs (either the first patch, or the +cover letter), like this: + +------------ +base-commit: P +prerequisite-patch-id: X +prerequisite-patch-id: Y +prerequisite-patch-id: Z +------------ + +For non-linear topology, such as + +................................................ +---P---X---A---M---C + \ / + Y---Z---B +................................................ + +You can also use `git format-patch --base=P -3 C` to generate patches +for A, B and C, and the identifiers for P, X, Y, Z are appended at the +end of the first message. + +If set `--base=auto` in cmdline, it will track base commit automatically, +the base commit will be the merge base of tip commit of the remote-tracking +branch and revision-range specified in cmdline. +For a local branch, you need to track a remote branch by `git branch +--set-upstream-to` before using this option. EXAMPLES -------- diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 8174d27efd..6364e5dc45 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -130,7 +130,12 @@ The template directory will be one of the following (in order): - the default template directory: `/usr/share/git-core/templates`. The default template directory includes some directory structure, suggested -"exclude patterns" (see linkgit:gitignore[5]), and sample hook files (see linkgit:githooks[5]). +"exclude patterns" (see linkgit:gitignore[5]), and sample hook files. + +The sample hooks are all disabled by default, To enable one of the +sample hooks rename it by removing its `.sample` suffix. + +See linkgit:githooks[5] for more general info on hook execution. EXAMPLES -------- diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 0947084140..3bbc731f67 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -85,7 +85,7 @@ with comments and suggestions on the message you are responding to, and to conclude it with a patch submission, separating the discussion and the beginning of the proposed commit log message with a scissors line. + -This can enabled by default with the configuration option mailinfo.scissors. +This can be enabled by default with the configuration option mailinfo.scissors. --no-scissors:: Ignore scissors lines. Useful for overriding mailinfo.scissors settings. diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index 8de349968a..9c4fd6812c 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -402,4 +402,4 @@ on the `notes.rewrite.<command>` and `notes.rewriteRef` settings. GIT --- -Part of the linkgit:git[7] suite +Part of the linkgit:git[1] suite diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 13adebf7b7..9226c4380c 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -13,7 +13,7 @@ SYNOPSIS [--reference <repository>] [--depth <depth>] [--] <repository> [<path>] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] -'git submodule' [--quiet] deinit [-f|--force] [--] <path>... +'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...) 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...] @@ -140,12 +140,15 @@ deinit:: tree. Further calls to `git submodule update`, `git submodule foreach` and `git submodule sync` will skip any unregistered submodules until they are initialized again, so use this command if you don't want to - have a local checkout of the submodule in your work tree anymore. If + have a local checkout of the submodule in your working tree anymore. If you really want to remove a submodule from the repository and commit that use linkgit:git-rm[1] instead. + -If `--force` is specified, the submodule's work tree will be removed even if -it contains local modifications. +When the command is run without pathspec, it errors out, +instead of deinit-ing everything, to prevent mistakes. ++ +If `--force` is specified, the submodule's working tree will +be removed even if it contains local modifications. update:: + @@ -247,6 +250,10 @@ OPTIONS --quiet:: Only print error messages. +--all:: + This option is only valid for the deinit command. Unregister all + submodules in the working tree. + -b:: --branch:: Branch of repository to add as submodule. @@ -257,8 +264,8 @@ OPTIONS --force:: This option is only valid for add, deinit and update commands. When running add, allow adding an otherwise ignored submodule path. - When running deinit the submodule work trees will be removed even if - they contain local changes. + When running deinit the submodule working trees will be removed even + if they contain local changes. When running update (only effective with the checkout procedure), throw away local changes in submodules when switching to a different commit; and always run a checkout operation in the diff --git a/Documentation/git.txt b/Documentation/git.txt index 34ff007a98..dd6dbf7dd9 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,11 +43,12 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.8.2/git.html[documentation for release 2.8.2] +* link:v2.8.3/git.html[documentation for release 2.8.3] * release notes for - link:RelNotes/2.8.2.txt[2.8.2]. - link:RelNotes/2.8.1.txt[2.8.1]. + link:RelNotes/2.8.3.txt[2.8.3], + link:RelNotes/2.8.2.txt[2.8.2], + link:RelNotes/2.8.1.txt[2.8.1], link:RelNotes/2.8.0.txt[2.8]. * link:v2.7.3/git.html[documentation for release 2.7.3] diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index a2f59b194c..d82e912e55 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -7,24 +7,35 @@ githooks - Hooks used by Git SYNOPSIS -------- -$GIT_DIR/hooks/* +$GIT_DIR/hooks/* (or \`git config core.hooksPath`/*) DESCRIPTION ----------- -Hooks are little scripts you can place in `$GIT_DIR/hooks` -directory to trigger action at certain points. When -'git init' is run, a handful of example hooks are copied into the -`hooks` directory of the new repository, but by default they are -all disabled. To enable a hook, rename it by removing its `.sample` -suffix. +Hooks are programs you can place in a hooks directory to trigger +actions at certain points in git's execution. Hooks that don't have +the executable bit set are ignored. -NOTE: It is also a requirement for a given hook to be executable. -However - in a freshly initialized repository - the `.sample` files are -executable by default. +By default the hooks directory is `$GIT_DIR/hooks`, but that can be +changed via the `core.hooksPath` configuration variable (see +linkgit:git-config[1]). -This document describes the currently defined hooks. +Before Git invokes a hook, it changes its working directory to either +the root of the working tree in a non-bare repository, or to the +$GIT_DIR in a bare repository. + +Hooks can get their arguments via the environment, command-line +arguments, and stdin. See the documentation for each hook below for +details. + +'git init' may copy hooks to the new repository, depending on its +configuration. See the "TEMPLATE DIRECTORY" section in +linkgit:git-init[1] for details. When the rest of this document refers +to "default hooks" it's talking about the default template shipped +with Git. + +The currently supported hooks are described below. HOOKS ----- @@ -32,15 +43,15 @@ HOOKS applypatch-msg ~~~~~~~~~~~~~~ -This hook is invoked by 'git am' script. It takes a single +This hook is invoked by 'git am'. It takes a single parameter, the name of the file that holds the proposed commit -log message. Exiting with non-zero status causes -'git am' to abort before applying the patch. +log message. Exiting with a non-zero status causes 'git am' to abort +before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard -format (if the project has one). It can also be used to refuse -the commit after inspecting the message file. +format. It can also be used to refuse the commit after inspecting +the message file. The default 'applypatch-msg' hook, when enabled, runs the 'commit-msg' hook, if the latter is enabled. @@ -73,10 +84,10 @@ pre-commit ~~~~~~~~~~ This hook is invoked by 'git commit', and can be bypassed -with `--no-verify` option. It takes no parameter, and is +with the `--no-verify` option. It takes no parameters, and is invoked before obtaining the proposed commit log message and -making a commit. Exiting with non-zero status from this script -causes the 'git commit' to abort. +making a commit. Exiting with a non-zero status from this script +causes the 'git commit' command to abort before creating a commit. The default 'pre-commit' hook, when enabled, catches introduction of lines with trailing whitespaces and aborts the commit when @@ -115,15 +126,15 @@ commit-msg ~~~~~~~~~~ This hook is invoked by 'git commit', and can be bypassed -with `--no-verify` option. It takes a single parameter, the +with the `--no-verify` option. It takes a single parameter, the name of the file that holds the proposed commit log message. -Exiting with non-zero status causes the 'git commit' to +Exiting with a non-zero status causes the 'git commit' to abort. -The hook is allowed to edit the message file in place, and can -be used to normalize the message into some project standard -format (if the project has one). It can also be used to refuse -the commit after inspecting the message file. +The hook is allowed to edit the message file in place, and can be used +to normalize the message into some project standard format. It +can also be used to refuse the commit after inspecting the message +file. The default 'commit-msg' hook, when enabled, detects duplicate "Signed-off-by" lines, and aborts the commit if one is found. @@ -131,8 +142,8 @@ The default 'commit-msg' hook, when enabled, detects duplicate post-commit ~~~~~~~~~~~ -This hook is invoked by 'git commit'. It takes no -parameter, and is invoked after a commit is made. +This hook is invoked by 'git commit'. It takes no parameters, and is +invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of 'git commit'. @@ -267,9 +278,11 @@ does not know the entire set of branches, so it would end up firing one e-mail per ref when used naively, though. The <<post-receive,'post-receive'>> hook is more suited to that. -Another use suggested on the mailing list is to use this hook to -implement access control which is finer grained than the one -based on filesystem group. +In an environment that restricts the users' access only to git +commands over the wire, this hook can be used to implement access +control without relying on filesystem ownership and group +membership. See linkgit:git-shell[1] for how you might use the login +shell to restrict the user's access to only git commands. Both standard output and standard error output are forwarded to 'git send-pack' on the other end, so you can simply `echo` messages diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index dfb43d000f..5b4a62e936 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -89,8 +89,11 @@ option can be used to override --squash. --verify-signatures:: --no-verify-signatures:: - Verify that the commits being merged have good and trusted GPG signatures - and abort the merge in case they do not. + Verify that the tip commit of the side branch being merged is + signed with a valid key, i.e. a key that has a valid uid: in the + default trust model, this means the signing key has been signed by + a trusted key. If the tip commit of the side branch is not signed + with a valid key, the merge is aborted. --summary:: --no-summary:: diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 671cebd95c..29b19b992f 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -143,8 +143,8 @@ ifndef::git-rev-list[] - '%N': commit notes endif::git-rev-list[] - '%GG': raw verification message from GPG for a signed commit -- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good, - untrusted signature and "N" for no signature +- '%G?': show "G" for a good (valid) signature, "B" for a bad signature, + "U" for a good signature with unknown validity and "N" for no signature - '%GS': show the name of the signer for a signed commit - '%GK': show the key used to sign a signed commit - '%gD': reflog selector, e.g., `refs/stash@{1}` diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt index e44426dd04..75368f26ca 100644 --- a/Documentation/technical/api-credentials.txt +++ b/Documentation/technical/api-credentials.txt @@ -243,7 +243,7 @@ appended to its command line, which is one of: The details of the credential will be provided on the helper's stdin stream. The exact format is the same as the input/output format of the `git credential` plumbing command (see the section `INPUT/OUTPUT -FORMAT` in linkgit:git-credential[7] for a detailed specification). +FORMAT` in linkgit:git-credential[1] for a detailed specification). For a `get` operation, the helper should produce a list of attributes on stdout in the same format. A helper is free to produce a subset, or @@ -268,4 +268,4 @@ See also linkgit:gitcredentials[7] -linkgit:git-config[5] (See configuration variables `credential.*`) +linkgit:git-config[1] (See configuration variables `credential.*`) diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 695bd4bf43..27bd701c0d 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -144,8 +144,12 @@ There are some macros to easily define options: `OPT_COUNTUP(short, long, &int_var, description)`:: Introduce a count-up option. - `int_var` is incremented on each use of `--option`, and - reset to zero with `--no-option`. + Each use of `--option` increments `int_var`, starting from zero + (even if initially negative), and `--no-option` resets it to + zero. To determine if `--option` or `--no-option` was encountered at + all, initialize `int_var` to a negative value, and if it is still + negative after parse_options(), then neither `--option` nor + `--no-option` was seen. `OPT_BIT(short, long, &int_var, description, mask)`:: Introduce a boolean option. diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index c6977bbc5a..8b36343802 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -526,7 +526,7 @@ Push Certificate A push certificate begins with a set of header lines. After the header and an empty line, the protocol commands follow, one per -line. Note that the the trailing LF in push-cert PKT-LINEs is _not_ +line. Note that the trailing LF in push-cert PKT-LINEs is _not_ optional; it must be present. Currently, the following header fields are defined: @@ -2062,7 +2062,7 @@ XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell \ --keyword=gettextln --keyword=eval_gettextln XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) -LOCALIZED_SH = $(SCRIPT_SH) +LOCALIZED_SH = $(SCRIPT_SH) git-parse-remote.sh LOCALIZED_PERL = $(SCRIPT_PERL) ifdef XGETTEXT_INCLUDE_TESTS @@ -860,8 +860,8 @@ static void check_good_are_ancestors_of_bad(const char *prefix, int no_checkout) /* Create file BISECT_ANCESTORS_OK. */ fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) - warning("could not create file '%s': %s", - filename, strerror(errno)); + warning_errno("could not create file '%s'", + filename); else close(fd); done: @@ -910,8 +910,7 @@ void read_bisect_terms(const char **read_bad, const char **read_good) *read_good = "good"; return; } else { - die("could not read file '%s': %s", filename, - strerror(errno)); + die_errno("could not read file '%s'", filename); } } else { strbuf_getline_lf(&str, fp); @@ -334,15 +334,16 @@ void remove_branch_state(void) unlink(git_path_squash_msg()); } -void die_if_checked_out(const char *branch) +void die_if_checked_out(const char *branch, int ignore_current_worktree) { - char *existing; + const struct worktree *wt; - existing = find_shared_symref("HEAD", branch); - if (existing) { - skip_prefix(branch, "refs/heads/", &branch); - die(_("'%s' is already checked out at '%s'"), branch, existing); - } + wt = find_shared_symref("HEAD", branch); + if (!wt || (ignore_current_worktree && wt->is_current)) + return; + skip_prefix(branch, "refs/heads/", &branch); + die(_("'%s' is already checked out at '%s'"), + branch, wt->path); } int replace_each_worktree_head_symref(const char *oldref, const char *newref) @@ -357,7 +358,8 @@ int replace_each_worktree_head_symref(const char *oldref, const char *newref) if (strcmp(oldref, worktrees[i]->head_ref)) continue; - if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) { + if (set_worktree_head_symref(get_worktree_git_dir(worktrees[i]), + newref)) { ret = -1; error(_("HEAD of working tree %s is not updated"), worktrees[i]->path); @@ -58,7 +58,7 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name); * worktree and die (with a message describing its checkout location) if * it is. */ -extern void die_if_checked_out(const char *branch); +extern void die_if_checked_out(const char *branch, int ignore_current_worktree); /* * Update all per-worktree HEADs pointing at the old ref to point the new ref. diff --git a/builtin/am.c b/builtin/am.c index d003939bc5..3dfe70b7a0 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -769,15 +769,15 @@ static int split_mail_conv(mail_conv_fn fn, struct am_state *state, in = fopen(*paths, "r"); if (!in) - return error(_("could not open '%s' for reading: %s"), - *paths, strerror(errno)); + return error_errno(_("could not open '%s' for reading"), + *paths); mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1); out = fopen(mail, "w"); if (!out) - return error(_("could not open '%s' for writing: %s"), - mail, strerror(errno)); + return error_errno(_("could not open '%s' for writing"), + mail); ret = fn(out, in, keep_cr); @@ -857,8 +857,7 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, fp = fopen(*paths, "r"); if (!fp) - return error(_("could not open '%s' for reading: %s"), *paths, - strerror(errno)); + return error_errno(_("could not open '%s' for reading"), *paths); while (!strbuf_getline_lf(&sb, fp)) { if (*sb.buf == '#') diff --git a/builtin/branch.c b/builtin/branch.c index 0adba629d2..2ecde53bf8 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -220,12 +220,12 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - char *worktree = find_shared_symref("HEAD", name); - if (worktree) { + const struct worktree *wt = + find_shared_symref("HEAD", name); + if (wt) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, worktree); - free(worktree); + bname.buf, wt->path); ret = 1; continue; } @@ -375,12 +375,14 @@ static char *get_head_description(void) strbuf_addf(&desc, _("(no branch, bisect started on %s)"), state.branch); else if (state.detached_from) { - /* TRANSLATORS: make sure these match _("HEAD detached at ") - and _("HEAD detached from ") in wt-status.c */ if (state.detached_at) + /* TRANSLATORS: make sure this matches + "HEAD detached at " in wt-status.c */ strbuf_addf(&desc, _("(HEAD detached at %s)"), state.detached_from); else + /* TRANSLATORS: make sure this matches + "HEAD detached from " in wt-status.c */ strbuf_addf(&desc, _("(HEAD detached from %s)"), state.detached_from); } @@ -524,6 +526,29 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin ref_array_clear(&array); } +static void reject_rebase_or_bisect_branch(const char *target) +{ + struct worktree **worktrees = get_worktrees(); + int i; + + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; + + if (!wt->is_detached) + continue; + + if (is_worktree_being_rebased(wt, target)) + die(_("Branch %s is being rebased at %s"), + target, wt->path); + + if (is_worktree_being_bisected(wt, target)) + die(_("Branch %s is being bisected at %s"), + target, wt->path); + } + + free_worktrees(worktrees); +} + static void rename_branch(const char *oldname, const char *newname, int force) { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; @@ -553,6 +578,8 @@ static void rename_branch(const char *oldname, const char *newname, int force) validate_new_branchname(newname, &newref, force, clobber_head_ok); + reject_rebase_or_bisect_branch(oldref.buf); + strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); @@ -593,8 +620,7 @@ static int edit_branch_description(const char *branch_name) branch_name, comment_line_char); if (write_file_gently(git_path(edit_description), "%s", buf.buf)) { strbuf_release(&buf); - return error(_("could not write branch description template: %s"), - strerror(errno)); + return error_errno(_("could not write branch description template")); } strbuf_reset(&buf); if (launch_editor(git_path(edit_description), &buf, NULL)) { @@ -630,7 +656,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) BRANCH_TRACK_EXPLICIT), OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"), BRANCH_TRACK_OVERRIDE), - OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"), + OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")), OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"), OPT__COLOR(&branch_use_color, N_("use colored output")), OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), @@ -838,8 +864,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (argc == 1 && track == BRANCH_TRACK_OVERRIDE && !branch_existed && remote_tracking) { fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name); - fprintf(stderr, _(" git branch -d %s\n"), branch->name); - fprintf(stderr, _(" git branch --set-upstream-to %s\n"), branch->name); + fprintf(stderr, " git branch -d %s\n", branch->name); + fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name); } } else diff --git a/builtin/checkout.c b/builtin/checkout.c index ea2fe1cf3f..3398c61e9a 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1110,7 +1110,7 @@ static int checkout_branch(struct checkout_opts *opts, char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); if (head_ref && (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) - die_if_checked_out(new->path); + die_if_checked_out(new->path, 1); free(head_ref); } diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 3feeffeab1..8a674bc9e7 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -33,10 +33,6 @@ static int commit_tree_config(const char *var, const char *value, void *cb) int status = git_gpg_config(var, value, NULL); if (status) return status; - if (!strcmp(var, "commit.gpgsign")) { - sign_commit = git_config_bool(var, value) ? "" : NULL; - return 0; - } return git_default_config(var, value, cb); } diff --git a/builtin/commit.c b/builtin/commit.c index 391126e58d..443ff9196d 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -114,6 +114,7 @@ static char *fixup_message, *squash_message; static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; +static int config_commit_verbose = -1; /* unspecified */ static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; static char *sign_commit; @@ -1515,6 +1516,11 @@ static int git_commit_config(const char *k, const char *v, void *cb) sign_commit = git_config_bool(k, v) ? "" : NULL; return 0; } + if (!strcmp(k, "commit.verbose")) { + int is_bool; + config_commit_verbose = git_config_bool_or_int(k, v, &is_bool); + return 0; + } status = git_gpg_config(k, v, NULL); if (status) @@ -1661,9 +1667,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (parse_commit(current_head)) die(_("could not parse HEAD commit")); } + verbose = -1; /* unspecified */ argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); + if (verbose == -1) + verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; + if (dry_run) return dry_run_commit(argc, argv, prefix, current_head, &s); index_file = prepare_index(argc, argv, prefix, current_head, 0); diff --git a/builtin/fetch.c b/builtin/fetch.c index f8455bde7a..1582ca7184 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -607,7 +607,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, fp = fopen(filename, "a"); if (!fp) - return error(_("cannot open %s: %s\n"), filename, strerror(errno)); + return error_errno(_("cannot open %s"), filename); if (raw_url) url = transport_anonymize_url(raw_url); @@ -848,7 +848,7 @@ static int truncate_fetch_head(void) FILE *fp = fopen_for_writing(filename); if (!fp) - return error(_("cannot open %s: %s\n"), filename, strerror(errno)); + return error_errno(_("cannot open %s"), filename); fclose(fp); return 0; } diff --git a/builtin/help.c b/builtin/help.c index 3c55ce4563..88480131cf 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -127,7 +127,7 @@ static void exec_woman_emacs(const char *path, const char *page) path = "emacsclient"; strbuf_addf(&man_page, "(woman \"%s\")", page); execlp(path, "emacsclient", "-e", man_page.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } } @@ -148,7 +148,7 @@ static void exec_man_konqueror(const char *path, const char *page) path = "kfmclient"; strbuf_addf(&man_page, "man:%s(1)", page); execlp(path, filename, "newTab", man_page.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } } @@ -157,7 +157,7 @@ static void exec_man_man(const char *path, const char *page) if (!path) path = "man"; execlp(path, "man", page, (char *)NULL); - warning(_("failed to exec '%s': %s"), path, strerror(errno)); + warning_errno(_("failed to exec '%s'"), path); } static void exec_man_cmd(const char *cmd, const char *page) @@ -165,7 +165,7 @@ static void exec_man_cmd(const char *cmd, const char *page) struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); - warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); + warning(_("failed to exec '%s'"), cmd); } static void add_man_viewer(const char *name) diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 2d1eb8bb8a..e8c71fc1d2 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1250,7 +1250,9 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha nr_unresolved * sizeof(*objects)); f = sha1fd(output_fd, curr_pack); fix_unresolved_deltas(f); - strbuf_addf(&msg, _("completed with %d local objects"), + strbuf_addf(&msg, Q_("completed with %d local object", + "completed with %d local objects", + nr_objects - nr_objects_initial), nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); strbuf_release(&msg); diff --git a/builtin/log.c b/builtin/log.c index dff3fbbb43..099f4f7be9 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -702,6 +702,7 @@ static void add_header(const char *value) #define THREAD_DEEP 2 static int thread; static int do_signoff; +static int base_auto; static const char *signature = git_version_string; static const char *signature_file; static int config_cover_letter; @@ -786,6 +787,10 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.outputdirectory")) return git_config_string(&config_output_directory, var, value); + if (!strcmp(var, "format.useautobase")) { + base_auto = git_config_bool(var, value); + return 0; + } return git_log_config(var, value, cb); } @@ -1191,6 +1196,155 @@ static int from_callback(const struct option *opt, const char *arg, int unset) return 0; } +struct base_tree_info { + struct object_id base_commit; + int nr_patch_id, alloc_patch_id; + struct object_id *patch_id; +}; + +static struct commit *get_base_commit(const char *base_commit, + struct commit **list, + int total) +{ + struct commit *base = NULL; + struct commit **rev; + int i = 0, rev_nr = 0; + + if (base_commit && strcmp(base_commit, "auto")) { + base = lookup_commit_reference_by_name(base_commit); + if (!base) + die(_("Unknown commit %s"), base_commit); + } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) { + struct branch *curr_branch = branch_get(NULL); + const char *upstream = branch_get_upstream(curr_branch, NULL); + if (upstream) { + struct commit_list *base_list; + struct commit *commit; + unsigned char sha1[20]; + + if (get_sha1(upstream, sha1)) + die(_("Failed to resolve '%s' as a valid ref."), upstream); + commit = lookup_commit_or_die(sha1, "upstream base"); + base_list = get_merge_bases_many(commit, total, list); + /* There should be one and only one merge base. */ + if (!base_list || base_list->next) + die(_("Could not find exact merge base.")); + base = base_list->item; + free_commit_list(base_list); + } else { + die(_("Failed to get upstream, if you want to record base commit automatically,\n" + "please use git branch --set-upstream-to to track a remote branch.\n" + "Or you could specify base commit by --base=<base-commit-id> manually.")); + } + } + + ALLOC_ARRAY(rev, total); + for (i = 0; i < total; i++) + rev[i] = list[i]; + + rev_nr = total; + /* + * Get merge base through pair-wise computations + * and store it in rev[0]. + */ + while (rev_nr > 1) { + for (i = 0; i < rev_nr / 2; i++) { + struct commit_list *merge_base; + merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); + if (!merge_base || merge_base->next) + die(_("Failed to find exact merge base")); + + rev[i] = merge_base->item; + } + + if (rev_nr % 2) + rev[i] = rev[2 * i]; + rev_nr = (rev_nr + 1) / 2; + } + + if (!in_merge_bases(base, rev[0])) + die(_("base commit should be the ancestor of revision list")); + + for (i = 0; i < total; i++) { + if (base == list[i]) + die(_("base commit shouldn't be in revision list")); + } + + free(rev); + return base; +} + +static void prepare_bases(struct base_tree_info *bases, + struct commit *base, + struct commit **list, + int total) +{ + struct commit *commit; + struct rev_info revs; + struct diff_options diffopt; + int i; + + if (!base) + return; + + diff_setup(&diffopt); + DIFF_OPT_SET(&diffopt, RECURSIVE); + diff_setup_done(&diffopt); + + oidcpy(&bases->base_commit, &base->object.oid); + + init_revisions(&revs, NULL); + revs.max_parents = 1; + revs.topo_order = 1; + for (i = 0; i < total; i++) { + list[i]->object.flags &= ~UNINTERESTING; + add_pending_object(&revs, &list[i]->object, "rev_list"); + list[i]->util = (void *)1; + } + base->object.flags |= UNINTERESTING; + add_pending_object(&revs, &base->object, "base"); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + /* + * Traverse the commits list, get prerequisite patch ids + * and stuff them in bases structure. + */ + while ((commit = get_revision(&revs)) != NULL) { + unsigned char sha1[20]; + struct object_id *patch_id; + if (commit->util) + continue; + if (commit_patch_id(commit, &diffopt, sha1)) + die(_("cannot get patch id")); + ALLOC_GROW(bases->patch_id, bases->nr_patch_id + 1, bases->alloc_patch_id); + patch_id = bases->patch_id + bases->nr_patch_id; + hashcpy(patch_id->hash, sha1); + bases->nr_patch_id++; + } +} + +static void print_bases(struct base_tree_info *bases) +{ + int i; + + /* Only do this once, either for the cover or for the first one */ + if (is_null_oid(&bases->base_commit)) + return; + + /* Show the base commit */ + printf("base-commit: %s\n", oid_to_hex(&bases->base_commit)); + + /* Show the prerequisite patches */ + for (i = bases->nr_patch_id - 1; i >= 0; i--) + printf("prerequisite-patch-id: %s\n", oid_to_hex(&bases->patch_id[i])); + + free(bases->patch_id); + bases->nr_patch_id = 0; + bases->alloc_patch_id = 0; + oidclr(&bases->base_commit); +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1215,6 +1369,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int reroll_count = -1; char *branch_name = NULL; char *from = NULL; + char *base_commit = NULL; + struct base_tree_info bases; + const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), @@ -1277,6 +1434,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback }, OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), + OPT_STRING(0, "base", &base_commit, N_("base-commit"), + N_("add prerequisite tree info to the patch series")), OPT_FILENAME(0, "signature-file", &signature_file, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), @@ -1514,6 +1673,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) signature = strbuf_detach(&buf, NULL); } + memset(&bases, 0, sizeof(bases)); + if (base_commit || base_auto) { + struct commit *base = get_base_commit(base_commit, list, nr); + reset_revision_walk(); + prepare_bases(&bases, base, list, nr); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { @@ -1527,6 +1693,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, origin, nr, list, branch_name, quiet); + print_bases(&bases); total++; start_number--; } @@ -1592,6 +1759,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.mime_boundary); else print_signature(); + print_bases(&bases); } if (!use_stdout) fclose(stdout); diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 104277acc4..4859ede38a 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -109,7 +109,7 @@ static int populate_maildir_list(struct string_list *list, const char *path) if ((dir = opendir(name)) == NULL) { if (errno == ENOENT) continue; - error("cannot opendir %s (%s)", name, strerror(errno)); + error_errno("cannot opendir %s", name); goto out; } @@ -174,12 +174,12 @@ static int split_maildir(const char *maildir, const char *dir, f = fopen(file, "r"); if (!f) { - error("cannot open mail %s (%s)", file, strerror(errno)); + error_errno("cannot open mail %s", file); goto out; } if (strbuf_getwholeline(&buf, f, '\n')) { - error("cannot read mail %s (%s)", file, strerror(errno)); + error_errno("cannot read mail %s", file); goto out; } @@ -210,7 +210,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, int file_done = 0; if (!f) { - error("cannot open mbox %s", file); + error_errno("cannot open mbox %s", file); goto out; } @@ -318,7 +318,7 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) } if (stat(arg, &argstat) == -1) { - error("cannot stat %s (%s)", arg, strerror(errno)); + error_errno("cannot stat %s", arg); return 1; } diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 55447053f2..13e22a2f0b 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -62,8 +62,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) usage_with_options(merge_file_usage, options); if (quiet) { if (!freopen("/dev/null", "w", stderr)) - return error("failed to redirect stderr to /dev/null: " - "%s", strerror(errno)); + return error_errno("failed to redirect stderr to /dev/null"); } if (prefix) @@ -95,12 +94,13 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) FILE *f = to_stdout ? stdout : fopen(fpath, "wb"); if (!f) - ret = error("Could not open %s for writing", filename); + ret = error_errno("Could not open %s for writing", + filename); else if (result.size && fwrite(result.ptr, result.size, 1, f) != 1) - ret = error("Could not write to %s", filename); + ret = error_errno("Could not write to %s", filename); else if (fclose(f)) - ret = error("Could not close %s", filename); + ret = error_errno("Could not close %s", filename); free(result.ptr); } diff --git a/builtin/notes.c b/builtin/notes.c index 6fd058de92..c65b59ad9a 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -847,15 +847,15 @@ static int merge(int argc, const char **argv, const char *prefix) update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ - char *existing; + const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - existing = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); - if (existing) + wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); + if (wt) die(_("A notes merge into %s is already in-progress at %s"), - default_notes_ref(), existing); + default_notes_ref(), wt->path); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", default_notes_ref()); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 14dccb5283..8f5e358e22 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -835,8 +835,7 @@ static void write_pack_file(void) * to preserve this property. */ if (stat(pack_tmp_name, &st) < 0) { - warning("failed to stat %s: %s", - pack_tmp_name, strerror(errno)); + warning_errno("failed to stat %s", pack_tmp_name); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { @@ -844,8 +843,7 @@ static void write_pack_file(void) utb.actime = st.st_atime; utb.modtime = --last_mtime; if (utime(pack_tmp_name, &utb) < 0) - warning("failed utime() on %s: %s", - pack_tmp_name, strerror(errno)); + warning_errno("failed utime() on %s", pack_tmp_name); } strbuf_addf(&tmpname, "%s-", base_name); diff --git a/builtin/pull.c b/builtin/pull.c index 596b92fc56..1d7333c8a1 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -478,13 +478,13 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); fprintf_ln(stderr, _("See git-pull(1) for details.")); fprintf(stderr, "\n"); - fprintf_ln(stderr, " git pull <remote> <branch>"); + fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>")); fprintf(stderr, "\n"); } else if (!curr_branch->merge_nr) { const char *remote_name = NULL; if (for_each_remote(get_only_remote, &remote_name) || !remote_name) - remote_name = "<remote>"; + remote_name = _("<remote>"); fprintf_ln(stderr, _("There is no tracking information for the current branch.")); if (opt_rebase) @@ -493,12 +493,12 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs fprintf_ln(stderr, _("Please specify which branch you want to merge with.")); fprintf_ln(stderr, _("See git-pull(1) for details.")); fprintf(stderr, "\n"); - fprintf_ln(stderr, " git pull <remote> <branch>"); + fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>")); fprintf(stderr, "\n"); - fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n" - "\n" - " git branch --set-upstream-to=%s/<branch> %s\n"), - remote_name, curr_branch->name); + fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:")); + fprintf(stderr, "\n"); + fprintf_ln(stderr, " git branch --set-upstream-to=%s/%s %s\n", + remote_name, _("<branch>"), curr_branch->name); } else fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n" "from the remote, but no such ref was fetched."), diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c index 7457c743e8..88eb8f9013 100644 --- a/builtin/remote-ext.c +++ b/builtin/remote-ext.c @@ -168,7 +168,7 @@ static int command_loop(const char *child) size_t i; if (!fgets(buffer, MAXCOMMAND - 1, stdin)) { if (ferror(stdin)) - die("Comammand input error"); + die("Command input error"); exit(0); } /* Strip end of line characters. */ diff --git a/builtin/remote.c b/builtin/remote.c index fda5c2e53d..d33766be39 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1154,6 +1154,8 @@ static int show(int argc, const char **argv) url_nr = states.remote->url_nr; } for (i = 0; i < url_nr; i++) + /* TRANSLATORS: the colon ':' should align with + the one in " Fetch URL: %s" translation */ printf_ln(_(" Push URL: %s"), url[i]); if (!i) printf_ln(_(" Push URL: %s"), "(no URL)"); diff --git a/builtin/rm.c b/builtin/rm.c index 8829b09d0b..8abb0207fa 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -152,7 +152,7 @@ static int check_local_mod(unsigned char *head, int index_only) if (lstat(ce->name, &st) < 0) { if (errno != ENOENT && errno != ENOTDIR) - warning("'%s': %s", ce->name, strerror(errno)); + warning_errno(_("failed to stat '%s'"), ce->name); /* It already vanished from the working tree */ continue; } @@ -314,7 +314,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); if (list.entry[list.nr++].is_submodule && !is_staging_gitmodules_ok()) - die (_("Please, stage your changes to .gitmodules or stash them to proceed")); + die (_("Please stage your changes to .gitmodules or stash them to proceed")); } if (pathspec.nr) { diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 3bd6883eff..8da263f0b0 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -9,6 +9,211 @@ #include "submodule-config.h" #include "string-list.h" #include "run-command.h" +#include "remote.h" +#include "refs.h" +#include "connect.h" + +static char *get_default_remote(void) +{ + char *dest = NULL, *ret; + unsigned char sha1[20]; + struct strbuf sb = STRBUF_INIT; + const char *refname = resolve_ref_unsafe("HEAD", 0, sha1, NULL); + + if (!refname) + die(_("No such ref: %s"), "HEAD"); + + /* detached HEAD */ + if (!strcmp(refname, "HEAD")) + return xstrdup("origin"); + + if (!skip_prefix(refname, "refs/heads/", &refname)) + die(_("Expecting a full ref name, got %s"), refname); + + strbuf_addf(&sb, "branch.%s.remote", refname); + if (git_config_get_string(sb.buf, &dest)) + ret = xstrdup("origin"); + else + ret = dest; + + strbuf_release(&sb); + return ret; +} + +static int starts_with_dot_slash(const char *str) +{ + return str[0] == '.' && is_dir_sep(str[1]); +} + +static int starts_with_dot_dot_slash(const char *str) +{ + return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]); +} + +/* + * Returns 1 if it was the last chop before ':'. + */ +static int chop_last_dir(char **remoteurl, int is_relative) +{ + char *rfind = find_last_dir_sep(*remoteurl); + if (rfind) { + *rfind = '\0'; + return 0; + } + + rfind = strrchr(*remoteurl, ':'); + if (rfind) { + *rfind = '\0'; + return 1; + } + + if (is_relative || !strcmp(".", *remoteurl)) + die(_("cannot strip one component off url '%s'"), + *remoteurl); + + free(*remoteurl); + *remoteurl = xstrdup("."); + return 0; +} + +/* + * The `url` argument is the URL that navigates to the submodule origin + * repo. When relative, this URL is relative to the superproject origin + * URL repo. The `up_path` argument, if specified, is the relative + * path that navigates from the submodule working tree to the superproject + * working tree. Returns the origin URL of the submodule. + * + * Return either an absolute URL or filesystem path (if the superproject + * origin URL is an absolute URL or filesystem path, respectively) or a + * relative file system path (if the superproject origin URL is a relative + * file system path). + * + * When the output is a relative file system path, the path is either + * relative to the submodule working tree, if up_path is specified, or to + * the superproject working tree otherwise. + * + * NEEDSWORK: This works incorrectly on the domain and protocol part. + * remote_url url outcome expectation + * http://a.com/b ../c http://a.com/c as is + * http://a.com/b ../../c http://c error out + * http://a.com/b ../../../c http:/c error out + * http://a.com/b ../../../../c http:c error out + * http://a.com/b ../../../../../c .:c error out + * NEEDSWORK: Given how chop_last_dir() works, this function is broken + * when a local part has a colon in its path component, too. + */ +static char *relative_url(const char *remote_url, + const char *url, + const char *up_path) +{ + int is_relative = 0; + int colonsep = 0; + char *out; + char *remoteurl = xstrdup(remote_url); + struct strbuf sb = STRBUF_INIT; + size_t len = strlen(remoteurl); + + if (is_dir_sep(remoteurl[len])) + remoteurl[len] = '\0'; + + if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl)) + is_relative = 0; + else { + is_relative = 1; + /* + * Prepend a './' to ensure all relative + * remoteurls start with './' or '../' + */ + if (!starts_with_dot_slash(remoteurl) && + !starts_with_dot_dot_slash(remoteurl)) { + strbuf_reset(&sb); + strbuf_addf(&sb, "./%s", remoteurl); + free(remoteurl); + remoteurl = strbuf_detach(&sb, NULL); + } + } + /* + * When the url starts with '../', remove that and the + * last directory in remoteurl. + */ + while (url) { + if (starts_with_dot_dot_slash(url)) { + url += 3; + colonsep |= chop_last_dir(&remoteurl, is_relative); + } else if (starts_with_dot_slash(url)) + url += 2; + else + break; + } + strbuf_reset(&sb); + strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url); + free(remoteurl); + + if (starts_with_dot_slash(sb.buf)) + out = xstrdup(sb.buf + 2); + else + out = xstrdup(sb.buf); + strbuf_reset(&sb); + + if (!up_path || !is_relative) + return out; + + strbuf_addf(&sb, "%s%s", up_path, out); + free(out); + return strbuf_detach(&sb, NULL); +} + +static int resolve_relative_url(int argc, const char **argv, const char *prefix) +{ + char *remoteurl = NULL; + char *remote = get_default_remote(); + const char *up_path = NULL; + char *res; + const char *url; + struct strbuf sb = STRBUF_INIT; + + if (argc != 2 && argc != 3) + die("resolve-relative-url only accepts one or two arguments"); + + url = argv[1]; + strbuf_addf(&sb, "remote.%s.url", remote); + free(remote); + + if (git_config_get_string(sb.buf, &remoteurl)) + /* the repository is its own authoritative upstream */ + remoteurl = xgetcwd(); + + if (argc == 3) + up_path = argv[2]; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} + +static int resolve_relative_url_test(int argc, const char **argv, const char *prefix) +{ + char *remoteurl, *res; + const char *up_path, *url; + + if (argc != 4) + die("resolve-relative-url-test only accepts three arguments: <up_path> <remoteurl> <url>"); + + up_path = argv[1]; + remoteurl = xstrdup(argv[2]); + url = argv[3]; + + if (!strcmp(up_path, "(null)")) + up_path = NULL; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} struct module_list { const struct cache_entry **entries; @@ -100,71 +305,142 @@ static int module_list(int argc, const char **argv, const char *prefix) return 0; } -static int module_name(int argc, const char **argv, const char *prefix) +static void init_submodule(const char *path, const char *prefix, int quiet) { const struct submodule *sub; + struct strbuf sb = STRBUF_INIT; + char *upd = NULL, *url = NULL, *displaypath; - if (argc != 2) - usage(_("git submodule--helper name <path>")); - + /* Only loads from .gitmodules, no overlay with .git/config */ gitmodules_config(); - sub = submodule_from_path(null_sha1, argv[1]); + + if (prefix) { + strbuf_addf(&sb, "%s%s", prefix, path); + displaypath = strbuf_detach(&sb, NULL); + } else + displaypath = xstrdup(path); + + sub = submodule_from_path(null_sha1, path); if (!sub) - die(_("no submodule mapping found in .gitmodules for path '%s'"), - argv[1]); + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); - printf("%s\n", sub->name); + /* + * Copy url setting when it is not set yet. + * To look up the url in .git/config, we must not fall back to + * .gitmodules, so look it up directly. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + if (git_config_get_string(sb.buf, &url)) { + url = xstrdup(sub->url); - return 0; + if (!url) + die(_("No url found for submodule path '%s' in .gitmodules"), + displaypath); + + /* Possibly a url relative to parent */ + if (starts_with_dot_dot_slash(url) || + starts_with_dot_slash(url)) { + char *remoteurl, *relurl; + char *remote = get_default_remote(); + struct strbuf remotesb = STRBUF_INIT; + strbuf_addf(&remotesb, "remote.%s.url", remote); + free(remote); + + if (git_config_get_string(remotesb.buf, &remoteurl)) + /* + * The repository is its own + * authoritative upstream + */ + remoteurl = xgetcwd(); + relurl = relative_url(remoteurl, url, NULL); + strbuf_release(&remotesb); + free(remoteurl); + free(url); + url = relurl; + } + + if (git_config_set_gently(sb.buf, url)) + die(_("Failed to register url for submodule path '%s'"), + displaypath); + if (!quiet) + fprintf(stderr, + _("Submodule '%s' (%s) registered for path '%s'\n"), + sub->name, url, displaypath); + } + + /* Copy "update" setting when it is not set yet */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.update", sub->name); + if (git_config_get_string(sb.buf, &upd) && + sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) { + fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"), + sub->name); + upd = xstrdup("none"); + } else + upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy)); + + if (git_config_set_gently(sb.buf, upd)) + die(_("Failed to register update mode for submodule path '%s'"), displaypath); + } + strbuf_release(&sb); + free(displaypath); + free(url); + free(upd); } -/* - * Rules to sanitize configuration variables that are Ok to be passed into - * submodule operations from the parent project using "-c". Should only - * include keys which are both (a) safe and (b) necessary for proper - * operation. - */ -static int submodule_config_ok(const char *var) +static int module_init(int argc, const char **argv, const char *prefix) { - if (starts_with(var, "credential.")) + struct pathspec pathspec; + struct module_list list = MODULE_LIST_INIT; + int quiet = 0; + int i; + + struct option module_init_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("alternative anchor for relative paths")), + OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper init [<path>]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_init_options, + git_submodule_helper_usage, 0); + + if (module_list_compute(argc, argv, prefix, &pathspec, &list) < 0) return 1; + + for (i = 0; i < list.nr; i++) + init_submodule(list.entries[i]->name, prefix, quiet); + return 0; } -static int sanitize_submodule_config(const char *var, const char *value, void *data) +static int module_name(int argc, const char **argv, const char *prefix) { - struct strbuf *out = data; + const struct submodule *sub; - if (submodule_config_ok(var)) { - if (out->len) - strbuf_addch(out, ' '); + if (argc != 2) + usage(_("git submodule--helper name <path>")); - if (value) - sq_quotef(out, "%s=%s", var, value); - else - sq_quote_buf(out, var); - } + gitmodules_config(); + sub = submodule_from_path(null_sha1, argv[1]); - return 0; -} + if (!sub) + die(_("no submodule mapping found in .gitmodules for path '%s'"), + argv[1]); -static void prepare_submodule_repo_env(struct argv_array *out) -{ - const char * const *var; - - for (var = local_repo_env; *var; var++) { - if (!strcmp(*var, CONFIG_DATA_ENVIRONMENT)) { - struct strbuf sanitized_config = STRBUF_INIT; - git_config_from_parameters(sanitize_submodule_config, - &sanitized_config); - argv_array_pushf(out, "%s=%s", *var, sanitized_config.buf); - strbuf_release(&sanitized_config); - } else { - argv_array_push(out, *var); - } - } + printf("%s\n", sub->name); + return 0; } static int clone_submodule(const char *path, const char *gitdir, const char *url, @@ -294,22 +570,6 @@ static int module_clone(int argc, const char **argv, const char *prefix) return 0; } -static int module_sanitize_config(int argc, const char **argv, const char *prefix) -{ - struct strbuf sanitized_config = STRBUF_INIT; - - if (argc > 1) - usage(_("git submodule--helper sanitize-config")); - - git_config_from_parameters(sanitize_submodule_config, &sanitized_config); - if (sanitized_config.len) - printf("%s\n", sanitized_config.buf); - - strbuf_release(&sanitized_config); - - return 0; -} - struct submodule_update_clone { /* index into 'list', the list of submodules to look into for cloning */ int current; @@ -336,6 +596,25 @@ struct submodule_update_clone { SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \ STRING_LIST_INIT_DUP, 0} + +static void next_submodule_warn_missing(struct submodule_update_clone *suc, + struct strbuf *out, const char *displaypath) +{ + /* + * Only mention uninitialized submodules when their + * paths have been specified. + */ + if (suc->warn_if_uninitialized) { + strbuf_addf(out, + _("Submodule path '%s' not initialized"), + displaypath); + strbuf_addch(out, '\n'); + strbuf_addstr(out, + _("Maybe you want to use 'update --init'?")); + strbuf_addch(out, '\n'); + } +} + /** * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise. @@ -370,6 +649,11 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, else displaypath = ce->name; + if (!sub) { + next_submodule_warn_missing(suc, out, displaypath); + goto cleanup; + } + if (suc->update.type == SM_UPDATE_NONE || (suc->update.type == SM_UPDATE_UNSPECIFIED && sub->update_strategy.type == SM_UPDATE_NONE)) { @@ -387,19 +671,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strbuf_addf(&sb, "submodule.%s.url", sub->name); git_config_get_string(sb.buf, &url); if (!url) { - /* - * Only mention uninitialized submodules when their - * path have been specified - */ - if (suc->warn_if_uninitialized) { - strbuf_addf(out, - _("Submodule path '%s' not initialized"), - displaypath); - strbuf_addch(out, '\n'); - strbuf_addstr(out, - _("Maybe you want to use 'update --init'?")); - strbuf_addch(out, '\n'); - } + next_submodule_warn_missing(suc, out, displaypath); goto cleanup; } @@ -570,8 +842,10 @@ static struct cmd_struct commands[] = { {"list", module_list}, {"name", module_name}, {"clone", module_clone}, - {"sanitize-config", module_sanitize_config}, - {"update-clone", update_clone} + {"update-clone", update_clone}, + {"resolve-relative-url", resolve_relative_url}, + {"resolve-relative-url-test", resolve_relative_url_test}, + {"init", module_init} }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/update-index.c b/builtin/update-index.c index 1c94ca59bf..b8b8522249 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -255,7 +255,7 @@ static int process_lstat_error(const char *path, int err) { if (err == ENOENT || err == ENOTDIR) return remove_one_path(path); - return error("lstat(\"%s\"): %s", path, strerror(errno)); + return error("lstat(\"%s\"): %s", path, strerror(err)); } static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st) diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index dbfe14f3fe..2caedf1849 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -104,8 +104,7 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) pfd[1].events = POLLIN; if (poll(pfd, 2, -1) < 0) { if (errno != EINTR) { - error("poll failed resuming: %s", - strerror(errno)); + error_errno("poll failed resuming"); sleep(1); } continue; diff --git a/builtin/worktree.c b/builtin/worktree.c index d8e3795dc4..96a2834a18 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -110,7 +110,7 @@ static void prune_worktrees(void) if (ret < 0 && errno == ENOTDIR) ret = unlink(path.buf); if (ret) - error(_("failed to remove: %s"), strerror(errno)); + error_errno(_("failed to remove '%s'"), path.buf); } closedir(dir); if (!show_only) @@ -205,7 +205,7 @@ static int add_worktree(const char *path, const char *refname, if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && ref_exists(symref.buf)) { /* it's a branch */ if (!opts->force) - die_if_checked_out(symref.buf); + die_if_checked_out(symref.buf, 0); } else { /* must be a commit */ commit = lookup_commit_reference_by_name(refname); if (!commit) @@ -349,7 +349,7 @@ static int add(int ac, const char **av, const char *prefix) if (!opts.force && !strbuf_check_branch_ref(&symref, opts.new_branch) && ref_exists(symref.buf)) - die_if_checked_out(symref.buf); + die_if_checked_out(symref.buf, 0); strbuf_release(&symref); } @@ -654,6 +654,7 @@ extern int warn_on_object_refname_ambiguity; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; extern const char *git_attributes_file; +extern const char *git_hooks_path; extern int zlib_compression_level; extern int core_compression_level; extern int core_compression_seen; @@ -700,6 +701,14 @@ extern int ref_paranoia; extern char comment_line_char; extern int auto_comment_line_char; +/* Windows only */ +enum hide_dotfiles_type { + HIDE_DOTFILES_FALSE = 0, + HIDE_DOTFILES_TRUE, + HIDE_DOTFILES_DOTGITONLY +}; +extern enum hide_dotfiles_type hide_dotfiles; + enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, @@ -799,11 +808,14 @@ extern void check_repository_format(void); */ 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))); 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))); +extern void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...) + __attribute__((format (printf, 2, 3))); extern char *git_path_buf(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); extern void strbuf_git_path_submodule(struct strbuf *sb, const char *path, diff --git a/check-racy.c b/check-racy.c index 00d92a1663..24b6542352 100644 --- a/check-racy.c +++ b/check-racy.c @@ -12,7 +12,7 @@ int main(int ac, char **av) struct stat st; if (lstat(ce->name, &st)) { - error("lstat(%s): %s", ce->name, strerror(errno)); + error_errno("lstat(%s)", ce->name); continue; } diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh new file mode 100755 index 0000000000..579d540d32 --- /dev/null +++ b/ci/test-documentation.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# +# Perform sanity checks on documentation and build it. +# + +set -e + +make check-builtins +make check-docs +make doc + +test -s Documentation/git.html +test -s Documentation/git.xml +test -s Documentation/git.1 diff --git a/combine-diff.c b/combine-diff.c index 0e1d4b0893..8f2313d502 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1005,8 +1005,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, struct strbuf buf = STRBUF_INIT; if (strbuf_readlink(&buf, elem->path, st.st_size) < 0) { - error("readlink(%s): %s", elem->path, - strerror(errno)); + error_errno("readlink(%s)", elem->path); return; } result_size = buf.len; diff --git a/compat/mingw.c b/compat/mingw.c index 0413d5c3cd..a8218e6f0f 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -286,6 +286,49 @@ int mingw_rmdir(const char *pathname) return ret; } +static inline int needs_hiding(const char *path) +{ + const char *basename; + + if (hide_dotfiles == HIDE_DOTFILES_FALSE) + return 0; + + /* We cannot use basename(), as it would remove trailing slashes */ + mingw_skip_dos_drive_prefix((char **)&path); + if (!*path) + return 0; + + for (basename = path; *path; path++) + if (is_dir_sep(*path)) { + do { + path++; + } while (is_dir_sep(*path)); + /* ignore trailing slashes */ + if (*path) + basename = path; + } + + if (hide_dotfiles == HIDE_DOTFILES_TRUE) + return *basename == '.'; + + assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY); + return !strncasecmp(".git", basename, 4) && + (!basename[4] || is_dir_sep(basename[4])); +} + +static int set_hidden_flag(const wchar_t *path, int set) +{ + DWORD original = GetFileAttributesW(path), modified; + if (set) + modified = original | FILE_ATTRIBUTE_HIDDEN; + else + modified = original & ~FILE_ATTRIBUTE_HIDDEN; + if (original == modified || SetFileAttributesW(path, modified)) + return 0; + errno = err_win_to_posix(GetLastError()); + return -1; +} + int mingw_mkdir(const char *path, int mode) { int ret; @@ -293,6 +336,8 @@ int mingw_mkdir(const char *path, int mode) if (xutftowcs_path(wpath, path) < 0) return -1; ret = _wmkdir(wpath); + if (!ret && needs_hiding(path)) + return set_hidden_flag(wpath, 1); return ret; } @@ -319,6 +364,21 @@ int mingw_open (const char *filename, int oflags, ...) if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) errno = EISDIR; } + if ((oflags & O_CREAT) && needs_hiding(filename)) { + /* + * Internally, _wopen() uses the CreateFile() API which errors + * out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was + * specified and an already existing file's attributes do not + * match *exactly*. As there is no mode or flag we can set that + * would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try + * again *without* the O_CREAT flag (that corresponds to the + * CREATE_ALWAYS flag of CreateFile()). + */ + if (fd < 0 && errno == EACCES) + fd = _wopen(wfilename, oflags & ~O_CREAT, mode); + if (fd >= 0 && set_hidden_flag(wfilename, 1)) + warning("could not mark '%s' as hidden.", filename); + } return fd; } @@ -350,6 +410,7 @@ int mingw_fgetc(FILE *stream) #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { + int hide = needs_hiding(filename); FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) @@ -357,12 +418,19 @@ FILE *mingw_fopen (const char *filename, const char *otype) if (xutftowcs_path(wfilename, filename) < 0 || xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; + if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) { + error("could not unhide %s", filename); + return NULL; + } file = _wfopen(wfilename, wotype); + if (file && hide && set_hidden_flag(wfilename, 1)) + warning("could not mark '%s' as hidden.", filename); return file; } FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) { + int hide = needs_hiding(filename); FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; if (filename && !strcmp(filename, "/dev/null")) @@ -370,7 +438,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) if (xutftowcs_path(wfilename, filename) < 0 || xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; + if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) { + error("could not unhide %s", filename); + return NULL; + } file = _wfreopen(wfilename, wotype, stream); + if (file && hide && set_hidden_flag(wfilename, 1)) + warning("could not mark '%s' as hidden.", filename); return file; } diff --git a/compat/mingw.h b/compat/mingw.h index 1de70ffd62..69bb43dc35 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -142,6 +142,7 @@ static inline int fcntl(int fd, int cmd, ...) #define sigemptyset(x) (void)0 static inline int sigaddset(sigset_t *set, int signum) { return 0; } +#define SIG_BLOCK 0 #define SIG_UNBLOCK 0 static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset) { return 0; } @@ -416,9 +417,6 @@ int mingw_offset_1st_component(const char *path); void mingw_open_html(const char *path); #define open_html mingw_open_html -void mingw_mark_as_git_dir(const char *dir); -#define mark_as_git_dir mingw_mark_as_git_dir - /** * Converts UTF-8 encoded string to UTF-16LE. * diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index dfbe6d8408..4293b53b17 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -147,7 +147,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir) if (errno || inleft) { /* * iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF - * MacOS X avoids illegal byte sequemces. + * MacOS X avoids illegal byte sequences. * If they occur on a mounted drive (e.g. NFS) it is not worth to * die() for that, but rather let the user see the original name */ diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index b6ed9e7462..1c164088fb 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -104,4 +104,11 @@ static inline void *pthread_getspecific(pthread_key_t key) return TlsGetValue(key); } +#ifndef __MINGW64_VERSION_MAJOR +static inline int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) +{ + return 0; +} +#endif + #endif /* PTHREAD_H */ diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c index b905aea31b..6c7c9b6053 100644 --- a/compat/win32/syslog.c +++ b/compat/win32/syslog.c @@ -28,13 +28,13 @@ void syslog(int priority, const char *fmt, ...) va_end(ap); if (str_len < 0) { - warning("vsnprintf failed: '%s'", strerror(errno)); + warning_errno("vsnprintf failed"); return; } str = malloc(st_add(str_len, 1)); if (!str) { - warning("malloc failed: '%s'", strerror(errno)); + warning_errno("malloc failed"); return; } @@ -45,7 +45,7 @@ void syslog(int priority, const char *fmt, ...) while ((pos = strstr(str, "%1")) != NULL) { str = realloc(str, st_add(++str_len, 1)); if (!str) { - warning("realloc failed: '%s'", strerror(errno)); + warning_errno("realloc failed"); return; } memmove(pos + 2, pos + 1, strlen(pos)); @@ -717,6 +717,9 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.attributesfile")) return git_config_pathname(&git_attributes_file, var, value); + if (!strcmp(var, "core.hookspath")) + return git_config_pathname(&git_hooks_path, var, value); + if (!strcmp(var, "core.bare")) { is_bare_repository_cfg = git_config_bool(var, value); return 0; @@ -803,8 +806,6 @@ static int git_default_core_config(const char *var, const char *value) if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { - if (core_eol == EOL_CRLF) - return error("core.autocrlf=input conflicts with core.eol=crlf"); auto_crlf = AUTO_CRLF_INPUT; return 0; } @@ -830,8 +831,6 @@ static int git_default_core_config(const char *var, const char *value) core_eol = EOL_NATIVE; else core_eol = EOL_UNSET; - if (core_eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT) - return error("core.autocrlf=input conflicts with core.eol=crlf"); return 0; } @@ -912,6 +911,14 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.hidedotfiles")) { + if (value && !strcasecmp(value, "dotgitonly")) + hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; + else + hide_dotfiles = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -2009,7 +2016,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, lock = xcalloc(1, sizeof(struct lock_file)); fd = hold_lock_file_for_update(lock, config_filename, 0); if (fd < 0) { - error("could not lock config file %s: %s", config_filename, strerror(errno)); + error_errno("could not lock config file %s", config_filename); free(store.key); ret = CONFIG_NO_LOCK; goto out_free; @@ -2023,8 +2030,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, free(store.key); if ( ENOENT != errno ) { - error("opening %s: %s", config_filename, - strerror(errno)); + error_errno("opening %s", config_filename); ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */ goto out_free; } @@ -2108,8 +2114,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, if (contents == MAP_FAILED) { if (errno == ENODEV && S_ISDIR(st.st_mode)) errno = EISDIR; - error("unable to mmap '%s': %s", - config_filename, strerror(errno)); + error_errno("unable to mmap '%s'", config_filename); ret = CONFIG_INVALID_FILE; contents = NULL; goto out_free; @@ -2118,8 +2123,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, in_fd = -1; if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { - error("chmod on %s failed: %s", - get_lock_file_path(lock), strerror(errno)); + error_errno("chmod on %s failed", get_lock_file_path(lock)); ret = CONFIG_NO_WRITE; goto out_free; } @@ -2175,8 +2179,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename, } if (commit_lock_file(lock) < 0) { - error("could not write config file %s: %s", config_filename, - strerror(errno)); + error_errno("could not write config file %s", config_filename); ret = CONFIG_NO_WRITE; lock = NULL; goto out_free; @@ -2327,8 +2330,8 @@ int git_config_rename_section_in_file(const char *config_filename, fstat(fileno(config_file), &st); if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) { - ret = error("chmod on %s failed: %s", - get_lock_file_path(lock), strerror(errno)); + ret = error_errno("chmod on %s failed", + get_lock_file_path(lock)); goto out; } @@ -2382,8 +2385,8 @@ int git_config_rename_section_in_file(const char *config_filename, fclose(config_file); unlock_and_out: if (commit_lock_file(lock) < 0) - ret = error("could not write config file %s: %s", - config_filename, strerror(errno)); + ret = error_errno("could not write config file %s", + config_filename); out: free(filename_buf); return ret; diff --git a/connected.c b/connected.c index 299c56090b..bf1b12e7ec 100644 --- a/connected.c +++ b/connected.c @@ -86,17 +86,14 @@ static int check_everything_connected_real(sha1_iterate_fn fn, memcpy(commit, sha1_to_hex(sha1), 40); if (write_in_full(rev_list.in, commit, 41) < 0) { if (errno != EPIPE && errno != EINVAL) - error(_("failed write to rev-list: %s"), - strerror(errno)); + error_errno(_("failed write to rev-list")); err = -1; break; } } while (!fn(cb_data, sha1)); - if (close(rev_list.in)) { - error(_("failed to close rev-list's stdin: %s"), strerror(errno)); - err = -1; - } + if (close(rev_list.in)) + err = error_errno(_("failed to close rev-list's stdin")); sigchain_pop(SIGPIPE); return finish_command(&rev_list) || err; diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES index 53c71b422a..100cc7a6d3 100644 --- a/contrib/hooks/multimail/CHANGES +++ b/contrib/hooks/multimail/CHANGES @@ -1,3 +1,11 @@ +Release 1.3.1 (bugfix-only release) +=================================== + +* Generate links to commits in combined emails (it was done only for + commit emails in 1.3.0). + +* Fix broken links on PyPi. + Release 1.3.0 ============= diff --git a/contrib/hooks/multimail/README b/contrib/hooks/multimail/README index 1e04801978..0c91d19a57 100644 --- a/contrib/hooks/multimail/README +++ b/contrib/hooks/multimail/README @@ -1,4 +1,4 @@ -git-multimail 1.3.0 +git-multimail 1.3.1 =================== .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git index ee1fa75f99..1210bde045 100644 --- a/contrib/hooks/multimail/README.Git +++ b/contrib/hooks/multimail/README.Git @@ -6,10 +6,10 @@ website: https://github.com/git-multimail/git-multimail The version in this directory was obtained from the upstream project -on May 03 2016 and consists of the "git-multimail" subdirectory from +on May 13 2016 and consists of the "git-multimail" subdirectory from revision - 26f3ae9f86aa7f8a054ba89235c4d3879f98b03d refs/tags/1.3.0 + 3ce5470d4abf7251604cbf64e73a962e1b617f5e refs/tags/1.3.1 Please see the README file in this directory for information about how to report bugs or contribute to git-multimail. diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py index f2c92aeed8..54ab4a4942 100755 --- a/contrib/hooks/multimail/git_multimail.py +++ b/contrib/hooks/multimail/git_multimail.py @@ -1,6 +1,6 @@ #! /usr/bin/env python -__version__ = '1.3.0' +__version__ = '1.3.1' # Copyright (c) 2015 Matthieu Moy and others # Copyright (c) 2012-2014 Michael Haggerty and others @@ -1704,6 +1704,14 @@ class BranchChange(ReferenceChange): self.header_template = COMBINED_HEADER_TEMPLATE self.intro_template = COMBINED_INTRO_TEMPLATE self.footer_template = COMBINED_FOOTER_TEMPLATE + + def revision_gen_link(base_url): + # revision is used only to generate the body, and + # _content_type is set while generating headers. Get it + # from the BranchChange object. + revision._content_type = self._content_type + return revision.generate_browse_link(base_url) + self.generate_browse_link = revision_gen_link for line in self.generate_email(push, body_filter, values): yield line @@ -1380,27 +1380,22 @@ static struct stream_filter *ident_filter(const unsigned char *sha1) struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1) { struct conv_attrs ca; - enum crlf_action crlf_action; struct stream_filter *filter = NULL; convert_attrs(&ca, path); - if (ca.drv && (ca.drv->smudge || ca.drv->clean)) - return filter; + return NULL; + + if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF) + return NULL; if (ca.ident) filter = ident_filter(sha1); - crlf_action = ca.crlf_action; - - if ((crlf_action == CRLF_BINARY) || - crlf_action == CRLF_AUTO_INPUT || - (crlf_action == CRLF_TEXT_INPUT)) - filter = cascade_filter(filter, &null_filter_singleton); - - else if (output_eol(crlf_action) == EOL_CRLF && - !(crlf_action == CRLF_AUTO || crlf_action == CRLF_AUTO_CRLF)) + if (output_eol(ca.crlf_action) == EOL_CRLF) filter = cascade_filter(filter, lf_to_crlf_filter()); + else + filter = cascade_filter(filter, &null_filter_singleton); return filter; } @@ -42,15 +42,15 @@ int copy_file(const char *dst, const char *src, int mode) status = copy_fd(fdi, fdo); switch (status) { case COPY_READ_ERROR: - error("copy-fd: read returned %s", strerror(errno)); + error_errno("copy-fd: read returned"); break; case COPY_WRITE_ERROR: - error("copy-fd: write returned %s", strerror(errno)); + error_errno("copy-fd: write returned"); break; } close(fdi); if (close(fdo) != 0) - return error("%s: close error: %s", dst, strerror(errno)); + return error_errno("%s: close error", dst); if (!status && adjust_shared_perm(dst)) return -1; diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 291c0fd5e9..1f14d56e98 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -179,12 +179,12 @@ static int serve_cache_loop(int fd) client = accept(fd, NULL, NULL); if (client < 0) { - warning("accept failed: %s", strerror(errno)); + warning_errno("accept failed"); return 1; } client2 = dup(client); if (client2 < 0) { - warning("dup failed: %s", strerror(errno)); + warning_errno("dup failed"); close(client); return 1; } diff --git a/diff-no-index.c b/diff-no-index.c index 03daadb25a..1f8999b9ca 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -65,8 +65,7 @@ static int populate_from_stdin(struct diff_filespec *s) size_t size = 0; if (strbuf_read(&buf, 0, 0) < 0) - return error("error while reading from stdin %s", - strerror(errno)); + return error_errno("error while reading from stdin"); s->should_munmap = 0; s->data = strbuf_detach(&buf, &size); @@ -53,13 +53,12 @@ static enum path_treatment read_directory_recursive(struct dir_struct *dir, int check_only, const struct path_simplify *simplify); static int get_dtype(struct dirent *de, const char *path, int len); -/* helper string functions with support for the ignore_case flag */ -int strcmp_icase(const char *a, const char *b) +int fspathcmp(const char *a, const char *b) { return ignore_case ? strcasecmp(a, b) : strcmp(a, b); } -int strncmp_icase(const char *a, const char *b, size_t count) +int fspathncmp(const char *a, const char *b, size_t count) { return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } @@ -795,12 +794,12 @@ int match_basename(const char *basename, int basenamelen, { if (prefix == patternlen) { if (patternlen == basenamelen && - !strncmp_icase(pattern, basename, basenamelen)) + !fspathncmp(pattern, basename, basenamelen)) return 1; } else if (flags & EXC_FLAG_ENDSWITH) { /* "*literal" matching against "fooliteral" */ if (patternlen - 1 <= basenamelen && - !strncmp_icase(pattern + 1, + !fspathncmp(pattern + 1, basename + basenamelen - (patternlen - 1), patternlen - 1)) return 1; @@ -837,7 +836,7 @@ int match_pathname(const char *pathname, int pathlen, */ if (pathlen < baselen + 1 || (baselen && pathname[baselen] != '/') || - strncmp_icase(pathname, base, baselen)) + fspathncmp(pathname, base, baselen)) return 0; namelen = baselen ? pathlen - baselen - 1 : pathlen; @@ -851,7 +850,7 @@ int match_pathname(const char *pathname, int pathlen, if (prefix > namelen) return 0; - if (strncmp_icase(pattern, name, prefix)) + if (fspathncmp(pattern, name, prefix)) return 0; pattern += prefix; patternlen -= prefix; @@ -270,8 +270,8 @@ extern int remove_dir_recursively(struct strbuf *path, int flag); /* tries to remove the path with empty directories along it, ignores ENOENT */ extern int remove_path(const char *path); -extern int strcmp_icase(const char *a, const char *b); -extern int strncmp_icase(const char *a, const char *b, size_t count); +extern int fspathcmp(const char *a, const char *b); +extern int fspathncmp(const char *a, const char *b, size_t count); /* * The prefix part of pattern must not contains wildcards. @@ -63,7 +63,6 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en if (!buffer) return 0; if (strbuf_read_file(buffer, path, 0) < 0) - return error("could not read file '%s': %s", - path, strerror(errno)); + return error_errno("could not read file '%s'", path); return 0; } @@ -168,8 +168,8 @@ static int write_entry(struct cache_entry *ce, ret = symlink(new, path); free(new); if (ret) - return error("unable to create symlink %s (%s)", - path, strerror(errno)); + return error_errno("unable to create symlink %s", + path); break; } @@ -186,8 +186,7 @@ static int write_entry(struct cache_entry *ce, fd = open_output_fd(path, ce, to_tempfile); if (fd < 0) { free(new); - return error("unable to create file %s (%s)", - path, strerror(errno)); + return error_errno("unable to create file %s", path); } wrote = write_in_full(fd, new, size); @@ -284,8 +283,7 @@ int checkout_entry(struct cache_entry *ce, return error("%s is a directory", path.buf); remove_subtree(&path); } else if (unlink(path.buf)) - return error("unable to unlink old '%s' (%s)", - path.buf, strerror(errno)); + return error_errno("unable to unlink old '%s'", path.buf); } else if (state->not_new) return 0; diff --git a/environment.c b/environment.c index 57acb2fe2a..ca72464a98 100644 --- a/environment.c +++ b/environment.c @@ -31,6 +31,7 @@ const char *git_log_output_encoding; const char *apply_default_whitespace; const char *apply_default_ignorewhitespace; const char *git_attributes_file; +const char *git_hooks_path; int zlib_compression_level = Z_BEST_SPEED; int core_compression_level; int core_compression_seen; @@ -63,6 +64,7 @@ int core_apply_sparse_checkout; int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; +enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 diff --git a/fast-import.c b/fast-import.c index 9fc7093406..83558dcfe3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -414,7 +414,7 @@ static void write_crash_report(const char *err) struct recent_command *rc; if (!rpt) { - error("can't write crash report %s: %s", loc, strerror(errno)); + error_errno("can't write crash report %s", loc); free(loc); return; } @@ -1512,7 +1512,7 @@ static int tree_content_set( t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { if (!*slash1) { if (!S_ISDIR(mode) && e->versions[1].mode == mode @@ -1602,7 +1602,7 @@ static int tree_content_remove( t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { if (*slash1 && !S_ISDIR(e->versions[1].mode)) /* * If p names a file in some subdirectory, and a @@ -1669,7 +1669,7 @@ static int tree_content_get( t = root->tree; for (i = 0; i < t->entry_count; i++) { e = t->entries[i]; - if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) { + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { if (!*slash1) goto found_entry; if (!S_ISDIR(e->versions[1].mode)) @@ -1806,8 +1806,8 @@ static void dump_marks(void) return; if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) { - failure |= error("Unable to write marks file %s: %s", - export_marks_file, strerror(errno)); + failure |= error_errno("Unable to write marks file %s", + export_marks_file); return; } @@ -1822,8 +1822,8 @@ static void dump_marks(void) dump_marks_helper(f, 0, marks); if (commit_lock_file(&mark_lock)) { - failure |= error("Unable to write file %s: %s", - export_marks_file, strerror(errno)); + failure |= error_errno("Unable to write file %s", + export_marks_file); return; } } @@ -59,6 +59,7 @@ FUNC(HAS_DOTGIT, WARN) \ FUNC(NULL_SHA1, WARN) \ FUNC(ZERO_PADDED_FILEMODE, WARN) \ + FUNC(NUL_IN_COMMIT, WARN) \ /* infos (reported as warnings, but ignored by default) */ \ FUNC(BAD_TAG_NAME, INFO) \ FUNC(MISSING_TAGGER_ENTRY, INFO) @@ -610,6 +611,7 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer, struct commit_graft *graft; unsigned parent_count, parent_line_count = 0, author_count; int err; + const char *buffer_begin = buffer; if (verify_headers(buffer, size, &commit->object, options)) return -1; @@ -666,9 +668,17 @@ static int fsck_commit_buffer(struct commit *commit, const char *buffer, err = fsck_ident(&buffer, &commit->object, options); if (err) return err; - if (!commit->tree) - return report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); - + if (!commit->tree) { + err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1)); + if (err) + return err; + } + if (memchr(buffer_begin, '\0', size)) { + err = report(options, &commit->object, FSCK_MSG_NUL_IN_COMMIT, + "NUL byte in the commit object body"); + if (err) + return err; + } return 0; } diff --git a/git-compat-util.h b/git-compat-util.h index 1f8b5f3b1f..49d4029b8d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -409,7 +409,9 @@ extern NORETURN void usagef(const char *err, ...) __attribute__((format (printf, extern NORETURN void die(const char *err, ...) __attribute__((format (printf, 1, 2))); extern NORETURN void die_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); extern int error(const char *err, ...) __attribute__((format (printf, 1, 2))); +extern int error_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))); +extern void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); #ifndef NO_OPENSSL #ifdef APPLE_COMMON_CRYPTO diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 02c0445be1..d50c85ed7b 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -1156,7 +1156,7 @@ sub prepDirForOutput # FUTURE: This would more accurately emulate CVS by sending # another copy of sticky after processing the files in that # directory. Or intermediate: perhaps send all sticky's for - # $seendirs after after processing all files. + # $seendirs after processing all files. } # update \n @@ -2824,7 +2824,7 @@ sub statecleanup } # Return working directory CVS revision "1.X" out -# of the the working directory "entries" state, for the given filename. +# of the working directory "entries" state, for the given filename. # This is prefixed with a dash if the file is scheduled for removal # when it is committed. sub revparse @@ -2935,7 +2935,7 @@ sub filecleanup return $filename; } -# Remove prependdir from the path, so that is is relative to the directory +# Remove prependdir from the path, so that it is relative to the directory # the CVS client was started from, rather than the top of the project. # Essentially the inverse of filecleanup(). sub remove_prependdir diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 55fe8d56c9..d3c39980f3 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -56,11 +56,13 @@ get_remote_merge_branch () { error_on_missing_default_upstream () { cmd="$1" op_type="$2" - op_prep="$3" + op_prep="$3" # FIXME: op_prep is no longer used example="$4" branch_name=$(git symbolic-ref -q HEAD) + display_branch_name="${branch_name#refs/heads/}" # If there's only one remote, use that in the suggestion - remote="<remote>" + remote="$(gettext "<remote>")" + branch="$(gettext "<branch>")" if test $(git remote | wc -l) = 1 then remote=$(git remote) @@ -68,22 +70,32 @@ error_on_missing_default_upstream () { if test -z "$branch_name" then - echo "You are not currently on a branch. Please specify which -branch you want to $op_type $op_prep. See git-${cmd}(1) for details. - - $example -" + gettextln "You are not currently on a branch." else - echo "There is no tracking information for the current branch. -Please specify which branch you want to $op_type $op_prep. -See git-${cmd}(1) for details - - $example - -If you wish to set tracking information for this branch you can do so with: - - git branch --set-upstream-to=$remote/<branch> ${branch_name#refs/heads/} -" + gettextln "There is no tracking information for the current branch." + fi + case "$op_type" in + rebase) + gettextln "Please specify which branch you want to rebase against." + ;; + merge) + gettextln "Please specify which branch you want to merge with." + ;; + *) + echo >&2 "BUG: unknown operation type: $op_type" + exit 1 + ;; + esac + eval_gettextln "See git-\${cmd}(1) for details." + echo + echo " $example" + echo + if test -n "$branch_name" + then + gettextln "If you wish to set tracking information for this branch you can do so with:" + echo + echo " git branch --set-upstream-to=$remote/$branch $display_branch_name" + echo fi exit 1 } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 9ea30756f1..1c6dfb6d56 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -82,6 +82,7 @@ rewritten_pending="$state_dir"/rewritten-pending cr=$(printf "\015") strategy_args=${strategy:+--strategy=$strategy} +test -n "$strategy_opts" && eval ' for strategy_opt in '"$strategy_opts"' do diff --git a/git-rebase.sh b/git-rebase.sh index 0bf41ee72b..44ede367ae 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -87,7 +87,10 @@ preserve_merges= autosquash= keep_empty= test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t -gpg_sign_opt= +case "$(git config --bool commit.gpgsign)" in +true) gpg_sign_opt=-S ;; +*) gpg_sign_opt= ;; +esac read_basic_state () { test -f "$state_dir/head-name" && diff --git a/git-submodule.sh b/git-submodule.sh index 2a84d7e66a..5a4dec050b 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -8,7 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /') USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] - or: $dashless [--quiet] deinit [-f|--force] [--] <path>... + or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...) or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--reference <repository>] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> @@ -46,79 +46,6 @@ prefix= custom_name= depth= -# The function takes at most 2 arguments. The first argument is the -# URL that navigates to the submodule origin repo. When relative, this URL -# is relative to the superproject origin URL repo. The second up_path -# argument, if specified, is the relative path that navigates -# from the submodule working tree to the superproject working tree. -# -# The output of the function is the origin URL of the submodule. -# -# The output will either be an absolute URL or filesystem path (if the -# superproject origin URL is an absolute URL or filesystem path, -# respectively) or a relative file system path (if the superproject -# origin URL is a relative file system path). -# -# When the output is a relative file system path, the path is either -# relative to the submodule working tree, if up_path is specified, or to -# the superproject working tree otherwise. -resolve_relative_url () -{ - remote=$(get_default_remote) - remoteurl=$(git config "remote.$remote.url") || - remoteurl=$(pwd) # the repository is its own authoritative upstream - url="$1" - remoteurl=${remoteurl%/} - sep=/ - up_path="$2" - - case "$remoteurl" in - *:*|/*) - is_relative= - ;; - ./*|../*) - is_relative=t - ;; - *) - is_relative=t - remoteurl="./$remoteurl" - ;; - esac - - while test -n "$url" - do - case "$url" in - ../*) - url="${url#../}" - case "$remoteurl" in - */*) - remoteurl="${remoteurl%/*}" - ;; - *:*) - remoteurl="${remoteurl%:*}" - sep=: - ;; - *) - if test -z "$is_relative" || test "." = "$remoteurl" - then - die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")" - else - remoteurl=. - fi - ;; - esac - ;; - ./*) - url="${url#./}" - ;; - *) - break;; - esac - done - remoteurl="$remoteurl$sep${url%/}" - echo "${is_relative:+${up_path}}${remoteurl#./}" -} - # Resolve a path to be relative to another path. This is intended for # converting submodule paths when git-submodule is run in a subdirectory # and only handles paths where the directory separator is '/'. @@ -197,9 +124,10 @@ isnumber() # of the settings from GIT_CONFIG_PARAMETERS. sanitize_submodule_env() { - sanitized_config=$(git submodule--helper sanitize-config) + save_config=$GIT_CONFIG_PARAMETERS clear_local_git_env - GIT_CONFIG_PARAMETERS=$sanitized_config + GIT_CONFIG_PARAMETERS=$save_config + export GIT_CONFIG_PARAMETERS } # @@ -291,7 +219,7 @@ cmd_add() die "$(gettext "Relative path can only be used from the toplevel of the working tree")" # dereference source url relative to parent's url - realrepo=$(resolve_relative_url "$repo") || exit + realrepo=$(git submodule--helper resolve-relative-url "$repo") || exit ;; *:*|/*) # absolute url @@ -477,50 +405,7 @@ cmd_init() shift done - git submodule--helper list --prefix "$wt_prefix" "$@" | - while read mode sha1 stage sm_path - do - die_if_unmatched "$mode" - name=$(git submodule--helper name "$sm_path") || exit - - displaypath=$(relative_path "$prefix$sm_path") - - # Copy url setting when it is not set yet - if test -z "$(git config "submodule.$name.url")" - then - url=$(git config -f .gitmodules submodule."$name".url) - test -z "$url" && - die "$(eval_gettext "No url found for submodule path '\$displaypath' in .gitmodules")" - - # Possibly a url relative to parent - case "$url" in - ./*|../*) - url=$(resolve_relative_url "$url") || exit - ;; - esac - git config submodule."$name".url "$url" || - die "$(eval_gettext "Failed to register url for submodule path '\$displaypath'")" - - say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$displaypath'")" - fi - - # Copy "update" setting when it is not set yet - if upd="$(git config -f .gitmodules submodule."$name".update)" && - test -n "$upd" && - test -z "$(git config submodule."$name".update)" - then - case "$upd" in - checkout | rebase | merge | none) - ;; # known modes of updating - *) - echo >&2 "warning: unknown update mode '$upd' suggested for submodule '$name'" - upd=none - ;; - esac - git config submodule."$name".update "$upd" || - die "$(eval_gettext "Failed to register update mode for submodule path '\$displaypath'")" - fi - done + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${GIT_QUIET:+--quiet} ${prefix:+--prefix "$prefix"} "$@" } # @@ -531,6 +416,7 @@ cmd_init() cmd_deinit() { # parse $args after "submodule ... deinit". + deinit_all= while test $# -ne 0 do case "$1" in @@ -540,6 +426,9 @@ cmd_deinit() -q|--quiet) GIT_QUIET=1 ;; + --all) + deinit_all=t + ;; --) shift break @@ -554,9 +443,14 @@ cmd_deinit() shift done - if test $# = 0 + if test -n "$deinit_all" && test "$#" -ne 0 + then + echo >&2 "$(eval_gettext "pathspec and --all are incompatible")" + usage + fi + if test $# = 0 && test -z "$deinit_all" then - die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")" + die "$(eval_gettext "Use '--all' if you really want to deinitialize all submodules")" fi git submodule--helper list --prefix "$wt_prefix" "$@" | @@ -823,7 +717,8 @@ cmd_update() if test -n "$recursive" then ( - prefix="$prefix$sm_path/" + prefix=$(relative_path "$prefix$sm_path/") + wt_prefix= sanitize_submodule_env cd "$sm_path" && eval cmd_update @@ -1212,9 +1107,9 @@ cmd_sync() # guarantee a trailing / up_path=${up_path%/}/ && # path from submodule work tree to submodule origin repo - sub_origin_url=$(resolve_relative_url "$url" "$up_path") && + sub_origin_url=$(git submodule--helper resolve-relative-url "$url" "$up_path") && # path from superproject work tree to submodule origin repo - super_config_url=$(resolve_relative_url "$url") || exit + super_config_url=$(git submodule--helper resolve-relative-url "$url") || exit ;; *) sub_origin_url="$url" diff --git a/gpg-interface.c b/gpg-interface.c index 2259938236..c4b1e8c78d 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -219,11 +219,9 @@ int verify_signed_buffer(const char *payload, size_t payload_size, args_gpg[0] = gpg_program; fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX"); if (fd < 0) - return error(_("could not create temporary file '%s': %s"), - path, strerror(errno)); + return error_errno(_("could not create temporary file '%s'"), path); if (write_in_full(fd, signature, signature_size) < 0) - return error(_("failed writing detached signature to '%s': %s"), - path, strerror(errno)); + return error_errno(_("failed writing detached signature to '%s'"), path); close(fd); gpg.argv = args_gpg; @@ -1732,7 +1732,7 @@ static int grep_source_load_file(struct grep_source *gs) if (lstat(filename, &st) < 0) { err_ret: if (errno != ENOENT) - error(_("'%s': %s"), filename, strerror(errno)); + error_errno(_("failed to stat '%s'"), filename); return -1; } if (!S_ISREG(st.st_mode)) @@ -1743,7 +1743,7 @@ static int grep_source_load_file(struct grep_source *gs) goto err_ret; data = xmallocz(size); if (st.st_size != read_in_full(i, data, size)) { - error(_("'%s': short read %s"), filename, strerror(errno)); + error_errno(_("'%s': short read"), filename); close(i); free(data); return -1; @@ -294,7 +294,7 @@ static int http_options(const char *var, const char *value, void *cb) return git_config_string(&http_proxy_authmethod, var, value); if (!strcmp("http.cookiefile", var)) - return git_config_string(&curl_cookie_file, var, value); + return git_config_pathname(&curl_cookie_file, var, value); if (!strcmp("http.savecookies", var)) { curl_save_cookies = git_config_bool(var, value); return 0; @@ -460,8 +460,7 @@ static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type) rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&ka, len); if (rc < 0) - warning("unable to set SO_KEEPALIVE on socket %s", - strerror(errno)); + warning_errno("unable to set SO_KEEPALIVE on socket"); return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */ } @@ -1923,8 +1922,7 @@ struct http_object_request *new_http_object_request(const char *base_url, } if (freq->localfile < 0) { - error("Couldn't create temporary file %s: %s", - freq->tmpfile, strerror(errno)); + error_errno("Couldn't create temporary file %s", freq->tmpfile); goto abort; } @@ -1969,8 +1967,8 @@ struct http_object_request *new_http_object_request(const char *base_url, prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); if (ftruncate(freq->localfile, 0) < 0) { - error("Couldn't truncate temporary file %s: %s", - freq->tmpfile, strerror(errno)); + error_errno("Couldn't truncate temporary file %s", + freq->tmpfile); goto abort; } } @@ -75,14 +75,12 @@ static int add_mailname_host(struct strbuf *buf) mailname = fopen("/etc/mailname", "r"); if (!mailname) { if (errno != ENOENT) - warning("cannot open /etc/mailname: %s", - strerror(errno)); + warning_errno("cannot open /etc/mailname"); return -1; } if (strbuf_getline(&mailnamebuf, mailname) == EOF) { if (ferror(mailname)) - warning("cannot read /etc/mailname: %s", - strerror(errno)); + warning_errno("cannot read /etc/mailname"); strbuf_release(&mailnamebuf); fclose(mailname); return -1; @@ -125,7 +123,7 @@ static void add_domainname(struct strbuf *out, int *is_bogus) char buf[1024]; if (gethostname(buf, sizeof(buf))) { - warning("cannot get host name: %s", strerror(errno)); + warning_errno("cannot get host name"); strbuf_addstr(out, "(none)"); *is_bogus = 1; return; diff --git a/ll-merge.c b/ll-merge.c index ff4a43a982..ad8be42f91 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -47,7 +47,9 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, assert(opts); /* - * The tentative merge result is the or common ancestor for an internal merge. + * The tentative merge result is the common ancestor for an + * internal merge. For the final merge, it is "ours" by + * default but -Xours/-Xtheirs can tweak the choice. */ if (opts->virtual_ancestor) { stolen = orig; @@ -383,8 +385,12 @@ int ll_merge(mmbuffer_t *result_buf, } } driver = find_ll_merge_driver(ll_driver_name); - if (opts->virtual_ancestor && driver->recursive) - driver = find_ll_merge_driver(driver->recursive); + + if (opts->virtual_ancestor) { + if (driver->recursive) + driver = find_ll_merge_driver(driver->recursive); + marker_size += 2; + } return driver->fn(driver, result_buf, path, ancestor, ancestor_label, ours, our_label, theirs, their_label, opts, marker_size); @@ -189,8 +189,7 @@ static int read_mailmap_file(struct string_list *map, const char *filename, if (!f) { if (errno == ENOENT) return 0; - return error("unable to open mailmap at %s: %s", - filename, strerror(errno)); + return error_errno("unable to open mailmap at %s", filename); } while (fgets(buffer, sizeof(buffer), f) != NULL) diff --git a/parse-options.c b/parse-options.c index 47a9192060..312a85dbde 100644 --- a/parse-options.c +++ b/parse-options.c @@ -110,6 +110,8 @@ static int get_value(struct parse_opt_ctx_t *p, return 0; case OPTION_COUNTUP: + if (*(int *)opt->value < 0) + *(int *)opt->value = 0; *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; return 0; diff --git a/patch-ids.c b/patch-ids.c index b7b3e5a1a7..a4d0016664 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -4,7 +4,7 @@ #include "sha1-lookup.h" #include "patch-ids.h" -static int commit_patch_id(struct commit *commit, struct diff_options *options, +int commit_patch_id(struct commit *commit, struct diff_options *options, unsigned char *sha1) { if (commit->parents) diff --git a/patch-ids.h b/patch-ids.h index c8c7ca110a..eeb56b307f 100644 --- a/patch-ids.h +++ b/patch-ids.h @@ -13,6 +13,8 @@ struct patch_ids { struct patch_id_bucket *patches; }; +int commit_patch_id(struct commit *commit, struct diff_options *options, + unsigned char *sha1); int init_patch_ids(struct patch_ids *); int free_patch_ids(struct patch_ids *); struct patch_id *add_commit_patch_id(struct commit *, struct patch_ids *); @@ -5,6 +5,7 @@ #include "strbuf.h" #include "string-list.h" #include "dir.h" +#include "worktree.h" static int get_st_mode_bits(const char *path, int *mode) { @@ -134,7 +135,7 @@ static struct common_dir common_list[] = { * definite * definition * - * The trie would look look like: + * The trie would look like: * root: len = 0, children a and d non-NULL, value = NULL. * a: len = 2, contents = bc, value = (data for "abc") * d: len = 2, contents = ef, children i non-NULL, value = (data for "def") @@ -383,10 +384,11 @@ static void adjust_git_path(struct strbuf *buf, int git_dir_len) update_common_dir(buf, git_dir_len, NULL); } -static void do_git_path(struct strbuf *buf, const char *fmt, va_list args) +static void do_git_path(const struct worktree *wt, struct strbuf *buf, + const char *fmt, va_list args) { int gitdir_len; - strbuf_addstr(buf, get_git_dir()); + strbuf_addstr(buf, get_worktree_git_dir(wt)); if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) strbuf_addch(buf, '/'); gitdir_len = buf->len; @@ -400,7 +402,7 @@ char *git_path_buf(struct strbuf *buf, const char *fmt, ...) va_list args; strbuf_reset(buf); va_start(args, fmt); - do_git_path(buf, fmt, args); + do_git_path(NULL, buf, fmt, args); va_end(args); return buf->buf; } @@ -409,7 +411,7 @@ void strbuf_git_path(struct strbuf *sb, const char *fmt, ...) { va_list args; va_start(args, fmt); - do_git_path(sb, fmt, args); + do_git_path(NULL, sb, fmt, args); va_end(args); } @@ -418,7 +420,7 @@ const char *git_path(const char *fmt, ...) struct strbuf *pathname = get_pathname(); va_list args; va_start(args, fmt); - do_git_path(pathname, fmt, args); + do_git_path(NULL, pathname, fmt, args); va_end(args); return pathname->buf; } @@ -428,7 +430,7 @@ char *git_pathdup(const char *fmt, ...) struct strbuf path = STRBUF_INIT; va_list args; va_start(args, fmt); - do_git_path(&path, fmt, args); + do_git_path(NULL, &path, fmt, args); va_end(args); return strbuf_detach(&path, NULL); } @@ -454,6 +456,16 @@ const char *mkpath(const char *fmt, ...) return cleanup_path(pathname->buf); } +const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) +{ + struct strbuf *pathname = get_pathname(); + va_list args; + va_start(args, fmt); + do_git_path(wt, pathname, fmt, args); + va_end(args); + return pathname->buf; +} + static void do_submodule_path(struct strbuf *buf, const char *path, const char *fmt, va_list args) { @@ -503,6 +515,35 @@ void strbuf_git_path_submodule(struct strbuf *buf, const char *path, va_end(args); } +static void do_git_common_path(struct strbuf *buf, + const char *fmt, + va_list args) +{ + strbuf_addstr(buf, get_git_common_dir()); + if (buf->len && !is_dir_sep(buf->buf[buf->len - 1])) + strbuf_addch(buf, '/'); + strbuf_vaddf(buf, fmt, args); + strbuf_cleanup_path(buf); +} + +const char *git_common_path(const char *fmt, ...) +{ + struct strbuf *pathname = get_pathname(); + va_list args; + va_start(args, fmt); + do_git_common_path(pathname, fmt, args); + va_end(args); + return pathname->buf; +} + +void strbuf_git_common_path(struct strbuf *sb, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + do_git_common_path(sb, fmt, args); + va_end(args); +} + int validate_headref(const char *path) { struct stat st; diff --git a/perl/Git.pm b/perl/Git.pm index 49eb88af8d..ce7e4e8da3 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -393,7 +393,7 @@ sub command_close_pipe { Execute the given C<COMMAND> in the same way as command_output_pipe() does but return both an input pipe filehandle and an output pipe filehandle. -The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>. +The function will return C<($pid, $pipe_in, $pipe_out, $ctx)>. See C<command_close_bidi_pipe()> for details. =cut diff --git a/reachable.c b/reachable.c index ed35201896..d0199cace4 100644 --- a/reachable.c +++ b/reachable.c @@ -119,8 +119,7 @@ static int add_recent_loose(const unsigned char *sha1, */ if (errno == ENOENT) return 0; - return error("unable to stat %s: %s", - sha1_to_hex(sha1), strerror(errno)); + return error_errno("unable to stat %s", sha1_to_hex(sha1)); } add_recent_object(sha1, st.st_mtime, data); @@ -2108,7 +2108,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) "Your branch and '%s' have diverged,\n" "and have %d and %d different commits each, " "respectively.\n", - theirs), + ours + theirs), base, ours, theirs); if (advice_status_hints) strbuf_addf(sb, @@ -501,8 +501,7 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output error("There were errors while writing %s (%s)", path, strerror(io.io.wrerror)); if (io.io.output && fclose(io.io.output)) - io.io.wrerror = error("Failed to flush %s: %s", - path, strerror(errno)); + io.io.wrerror = error_errno("Failed to flush %s", path); if (hunk_no < 0) { if (output) @@ -684,20 +683,17 @@ static int merge(const struct rerere_id *id, const char *path) * Mark that "postimage" was used to help gc. */ if (utime(rerere_path(id, "postimage"), NULL) < 0) - warning("failed utime() on %s: %s", - rerere_path(id, "postimage"), - strerror(errno)); + warning_errno("failed utime() on %s", + rerere_path(id, "postimage")); /* Update "path" with the resolution */ f = fopen(path, "w"); if (!f) - return error("Could not open %s: %s", path, - strerror(errno)); + return error_errno("Could not open %s", path); if (fwrite(result.ptr, result.size, 1, f) != 1) - error("Could not write %s: %s", path, strerror(errno)); + error_errno("Could not write %s", path); if (fclose(f)) - return error("Writing %s failed: %s", path, - strerror(errno)); + return error_errno("Writing %s failed", path); out: free(cur.ptr); @@ -1071,7 +1067,7 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) if (unlink(filename)) return (errno == ENOENT ? error("no remembered resolution for %s", path) - : error("cannot unlink %s: %s", filename, strerror(errno))); + : error_errno("cannot unlink %s", filename)); /* * Update the preimage so that the user can resolve the diff --git a/run-command.c b/run-command.c index e4593cd99b..af0c8a10df 100644 --- a/run-command.c +++ b/run-command.c @@ -233,7 +233,7 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) if (waiting < 0) { failed_errno = errno; - error("waitpid for %s failed: %s", argv0, strerror(errno)); + error_errno("waitpid for %s failed", argv0); } else if (waiting != pid) { error("waitpid is confused (%s)", argv0); } else if (WIFSIGNALED(status)) { @@ -420,8 +420,7 @@ fail_pipe: } } if (cmd->pid < 0) - error("cannot fork() for %s: %s", cmd->argv[0], - strerror(errno)); + error_errno("cannot fork() for %s", cmd->argv[0]); else if (cmd->clean_on_exit) mark_child_for_cleanup(cmd->pid); @@ -482,7 +481,7 @@ fail_pipe: cmd->dir, fhin, fhout, fherr); failed_errno = errno; if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) - error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); + error_errno("cannot spawn %s", cmd->argv[0]); if (cmd->clean_on_exit && cmd->pid >= 0) mark_child_for_cleanup(cmd->pid); @@ -703,7 +702,7 @@ int start_async(struct async *async) if (pipe(fdin) < 0) { if (async->out > 0) close(async->out); - return error("cannot create pipe: %s", strerror(errno)); + return error_errno("cannot create pipe"); } async->in = fdin[1]; } @@ -715,7 +714,7 @@ int start_async(struct async *async) close_pair(fdin); else if (async->in) close(async->in); - return error("cannot create pipe: %s", strerror(errno)); + return error_errno("cannot create pipe"); } async->out = fdout[0]; } @@ -740,7 +739,7 @@ int start_async(struct async *async) async->pid = fork(); if (async->pid < 0) { - error("fork (async) failed: %s", strerror(errno)); + error_errno("fork (async) failed"); goto error; } if (!async->pid) { @@ -787,7 +786,7 @@ int start_async(struct async *async) { int err = pthread_create(&async->tid, NULL, run_thread, async); if (err) { - error("cannot create thread: %s", strerror(err)); + error_errno("cannot create thread"); goto error; } } @@ -825,7 +824,10 @@ const char *find_hook(const char *name) static struct strbuf path = STRBUF_INIT; strbuf_reset(&path); - strbuf_git_path(&path, "hooks/%s", name); + if (git_hooks_path) + strbuf_addf(&path, "%s/%s", git_hooks_path, name); + else + strbuf_git_path(&path, "hooks/%s", name); if (access(path.buf, X_OK) < 0) return NULL; return path.buf; diff --git a/sequencer.c b/sequencer.c index e66f2fe0f0..4687ad4b80 100644 --- a/sequencer.c +++ b/sequencer.c @@ -875,8 +875,7 @@ static int sequencer_rollback(struct replay_opts *opts) return rollback_single_pick(); } if (!f) - return error(_("cannot open %s: %s"), git_path_head_file(), - strerror(errno)); + return error_errno(_("cannot open %s"), git_path_head_file()); if (strbuf_getline_lf(&buf, f)) { error(_("cannot read %s: %s"), git_path_head_file(), ferror(f) ? strerror(errno) : _("unexpected end of file")); diff --git a/server-info.c b/server-info.c index 5a86e297b5..75dd677413 100644 --- a/server-info.c +++ b/server-info.c @@ -36,7 +36,7 @@ static int update_info_file(char *path, int (*generate)(FILE *)) out: if (ret) { - error("unable to update %s: %s", path, strerror(errno)); + error_errno("unable to update %s", path); if (fp) fclose(fp); else if (fd >= 0) diff --git a/sha1_file.c b/sha1_file.c index d0f2aa029b..d5e11217f5 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -301,7 +301,7 @@ static int link_alt_odb_entry(const char *entry, const char *relative_base, return -1; } } - if (!strcmp_icase(ent->base, normalized_objdir)) { + if (!fspathcmp(ent->base, normalized_objdir)) { free(ent); return -1; } @@ -1107,9 +1107,8 @@ unsigned char *use_pack(struct packed_git *p, PROT_READ, MAP_PRIVATE, p->pack_fd, win->offset); if (win->base == MAP_FAILED) - die("packfile %s cannot be mapped: %s", - p->pack_name, - strerror(errno)); + die_errno("packfile %s cannot be mapped", + p->pack_name); if (!win->offset && win->len == p->pack_size && !p->do_not_close) close_pack_fd(p); @@ -1279,8 +1278,8 @@ static void prepare_packed_git_one(char *objdir, int local) dir = opendir(path.buf); if (!dir) { if (errno != ENOENT) - error("unable to open object pack directory: %s: %s", - path.buf, strerror(errno)); + error_errno("unable to open object pack directory: %s", + path.buf); strbuf_release(&path); return; } @@ -2984,7 +2983,7 @@ int finalize_object_file(const char *tmpfile, const char *filename) unlink_or_warn(tmpfile); if (ret) { if (ret != EEXIST) { - return error("unable to write sha1 filename %s: %s", filename, strerror(ret)); + return error_errno("unable to write sha1 filename %s", filename); } /* FIXME!!! Collision check here ? */ } @@ -2998,7 +2997,7 @@ out: static int write_buffer(int fd, const void *buf, size_t len) { if (write_in_full(fd, buf, len) < 0) - return error("file write error (%s)", strerror(errno)); + return error_errno("file write error"); return 0; } @@ -3081,7 +3080,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, if (errno == EACCES) return error("insufficient permission for adding an object to repository database %s", get_object_directory()); else - return error("unable to create temporary file: %s", strerror(errno)); + return error_errno("unable to create temporary file"); } /* Set it up */ @@ -3126,8 +3125,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, utb.actime = mtime; utb.modtime = mtime; if (utime(tmp_file.buf, &utb) < 0) - warning("failed utime() on %s: %s", - tmp_file.buf, strerror(errno)); + warning_errno("failed utime() on %s", tmp_file.buf); } return finalize_object_file(tmp_file.buf, filename); @@ -3360,7 +3358,7 @@ static int index_core(unsigned char *sha1, int fd, size_t size, if (size == read_in_full(fd, buf, size)) ret = index_mem(sha1, buf, size, type, path, flags); else - ret = error("short read %s", strerror(errno)); + ret = error_errno("short read"); free(buf); } else { void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); @@ -3425,18 +3423,14 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned case S_IFREG: fd = open(path, O_RDONLY); if (fd < 0) - return error("open(\"%s\"): %s", path, - strerror(errno)); + return error_errno("open(\"%s\")", path); if (index_fd(sha1, fd, st, OBJ_BLOB, path, flags) < 0) return error("%s: failed to insert into database", path); break; case S_IFLNK: - if (strbuf_readlink(&sb, path, st->st_size)) { - char *errstr = strerror(errno); - return error("readlink(\"%s\"): %s", path, - errstr); - } + if (strbuf_readlink(&sb, path, st->st_size)) + return error_errno("readlink(\"%s\")", path); if (!(flags & HASH_WRITE_OBJECT)) hash_sha1_file(sb.buf, sb.len, blob_type, sha1); else if (write_sha1_file(sb.buf, sb.len, blob_type, sha1)) @@ -3492,7 +3486,7 @@ static int for_each_file_in_obj_subdir(int subdir_nr, if (!dir) { if (errno == ENOENT) return 0; - return error("unable to open %s: %s", path->buf, strerror(errno)); + return error_errno("unable to open %s", path->buf); } while ((de = readdir(dir))) { diff --git a/split-index.c b/split-index.c index 968b780a06..3c75d4b9ce 100644 --- a/split-index.c +++ b/split-index.c @@ -60,7 +60,7 @@ static void mark_base_index_entries(struct index_state *base) * To keep track of the shared entries between * istate->base->cache[] and istate->cache[], base entry * position is stored in each base entry. All positions start - * from 1 instead of 0, which is resrved to say "this is a new + * from 1 instead of 0, which is reserved to say "this is a new * entry". */ for (i = 0; i < base->cache_nr; i++) diff --git a/submodule.c b/submodule.c index 90825e17fa..4532b11d66 100644 --- a/submodule.c +++ b/submodule.c @@ -13,6 +13,7 @@ #include "argv-array.h" #include "blob.h" #include "thread-utils.h" +#include "quote.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; static int parallel_jobs = 1; @@ -237,6 +238,27 @@ int parse_submodule_update_strategy(const char *value, return 0; } +const char *submodule_strategy_to_string(const struct submodule_update_strategy *s) +{ + struct strbuf sb = STRBUF_INIT; + switch (s->type) { + case SM_UPDATE_CHECKOUT: + return "checkout"; + case SM_UPDATE_MERGE: + return "merge"; + case SM_UPDATE_REBASE: + return "rebase"; + case SM_UPDATE_NONE: + return "none"; + case SM_UPDATE_UNSPECIFIED: + return NULL; + case SM_UPDATE_COMMAND: + strbuf_addf(&sb, "!%s", s->command); + return strbuf_detach(&sb, NULL); + } + return NULL; +} + void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -393,7 +415,7 @@ static int submodule_needs_pushing(const char *path, const unsigned char sha1[20 argv[1] = sha1_to_hex(sha1); cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; @@ -480,7 +502,7 @@ static int push_submodule(const char *path) const char *argv[] = {"push", NULL}; cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.dir = path; @@ -526,7 +548,7 @@ static int is_submodule_commit_present(const char *path, unsigned char sha1[20]) argv[3] = sha1_to_hex(sha1); cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.dir = path; @@ -709,7 +731,7 @@ static int get_next_submodule(struct child_process *cp, if (is_directory(git_dir)) { child_process_init(cp); cp->dir = strbuf_detach(&submodule_path, NULL); - cp->env = local_repo_env; + prepare_submodule_repo_env(&cp->env_array); cp->git_cmd = 1; if (!spf->quiet) strbuf_addf(err, "Fetching submodule %s%s\n", @@ -824,7 +846,7 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked) argv[2] = "-uno"; cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; @@ -885,7 +907,7 @@ int submodule_uses_gitfile(const char *path) /* Now test that all nested submodules use a gitfile too */ cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.no_stderr = 1; @@ -918,7 +940,7 @@ int ok_to_remove_submodule(const char *path) return 0; cp.argv = argv; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.no_stdin = 1; cp.out = -1; @@ -1129,3 +1151,13 @@ int parallel_submodules(void) { return parallel_jobs; } + +void prepare_submodule_repo_env(struct argv_array *out) +{ + const char * const *var; + + for (var = local_repo_env; *var; var++) { + if (strcmp(*var, CONFIG_DATA_ENVIRONMENT)) + argv_array_push(out, *var); + } +} diff --git a/submodule.h b/submodule.h index 7ef3775184..2af9390998 100644 --- a/submodule.h +++ b/submodule.h @@ -39,6 +39,7 @@ int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); int parse_submodule_update_strategy(const char *value, struct submodule_update_strategy *dst); +const char *submodule_strategy_to_string(const struct submodule_update_strategy *s); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, @@ -61,4 +62,11 @@ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); int parallel_submodules(void); +/* + * Prepare the "env_array" parameter of a "struct child_process" for executing + * a submodule by clearing any repo-specific envirionment variables, but + * retaining any config in the environment. + */ +void prepare_submodule_repo_env(struct argv_array *out); + #endif @@ -84,9 +84,9 @@ appropriately before running "make". -x:: Turn on shell tracing (i.e., `set -x`) during the tests - themselves. Implies `--verbose`. Note that this can cause - failures in some tests which redirect and test the - output of shell functions. Use with caution. + themselves. Implies `--verbose`. Note that in non-bash shells, + this can cause failures in some tests which redirect and test + the output of shell functions. Use with caution. -d:: --debug:: diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 2c8c8f18ed..8a1235d03e 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -7,16 +7,25 @@ static int integer = 0; static unsigned long magnitude = 0; static unsigned long timestamp; static int abbrev = 7; -static int verbose = 0, dry_run = 0, quiet = 0; +static int verbose = -1; /* unspecified */ +static int dry_run = 0, quiet = 0; static char *string = NULL; static char *file = NULL; static int ambiguous; static struct string_list list; +static struct { + int called; + const char *arg; + int unset; +} length_cb; + static int length_callback(const struct option *opt, const char *arg, int unset) { - printf("Callback: \"%s\", %d\n", - (arg ? arg : "not set"), unset); + length_cb.called = 1; + length_cb.arg = arg; + length_cb.unset = unset; + if (unset) return 1; /* do not support unset */ @@ -30,6 +39,61 @@ static int number_callback(const struct option *opt, const char *arg, int unset) return 0; } +static int collect_expect(const struct option *opt, const char *arg, int unset) +{ + struct string_list *expect; + struct string_list_item *item; + struct strbuf label = STRBUF_INIT; + const char *colon; + + if (!arg || unset) + die("malformed --expect option"); + + expect = (struct string_list *)opt->value; + colon = strchr(arg, ':'); + if (!colon) + die("malformed --expect option, lacking a colon"); + strbuf_add(&label, arg, colon - arg); + item = string_list_insert(expect, strbuf_detach(&label, NULL)); + if (item->util) + die("malformed --expect option, duplicate %s", label.buf); + item->util = (void *)arg; + return 0; +} + +__attribute__((format (printf,3,4))) +static void show(struct string_list *expect, int *status, const char *fmt, ...) +{ + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + va_list args; + + va_start(args, fmt); + strbuf_vaddf(&buf, fmt, args); + va_end(args); + + if (!expect->nr) + printf("%s\n", buf.buf); + else { + char *colon = strchr(buf.buf, ':'); + if (!colon) + die("malformed output format, output lacking colon: %s", fmt); + *colon = '\0'; + item = string_list_lookup(expect, buf.buf); + *colon = ':'; + if (!item) + ; /* not among entries being checked */ + else { + if (strcmp((const char *)item->util, buf.buf)) { + printf("-%s\n", (char *)item->util); + printf("+%s\n", buf.buf); + *status = 1; + } + } + } + strbuf_release(&buf); +} + int main(int argc, char **argv) { const char *prefix = "prefix/"; @@ -37,6 +101,7 @@ int main(int argc, char **argv) "test-parse-options <options>", NULL }; + struct string_list expect = STRING_LIST_INIT_NODUP; struct option options[] = { OPT_BOOL(0, "yes", &boolean, "get a boolean"), OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), @@ -77,28 +142,38 @@ int main(int argc, char **argv) OPT__VERBOSE(&verbose, "be verbose"), OPT__DRY_RUN(&dry_run, "dry run"), OPT__QUIET(&quiet, "be quiet"), + OPT_CALLBACK(0, "expect", &expect, "string", + "expected output in the variable dump", + collect_expect), OPT_END(), }; int i; + int ret = 0; argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); - printf("boolean: %d\n", boolean); - printf("integer: %d\n", integer); - printf("magnitude: %lu\n", magnitude); - printf("timestamp: %lu\n", timestamp); - printf("string: %s\n", string ? string : "(not set)"); - printf("abbrev: %d\n", abbrev); - printf("verbose: %d\n", verbose); - printf("quiet: %s\n", quiet ? "yes" : "no"); - printf("dry run: %s\n", dry_run ? "yes" : "no"); - printf("file: %s\n", file ? file : "(not set)"); + if (length_cb.called) { + const char *arg = length_cb.arg; + int unset = length_cb.unset; + show(&expect, &ret, "Callback: \"%s\", %d", + (arg ? arg : "not set"), unset); + } + show(&expect, &ret, "boolean: %d", boolean); + show(&expect, &ret, "integer: %d", integer); + show(&expect, &ret, "magnitude: %lu", magnitude); + show(&expect, &ret, "timestamp: %lu", timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); + show(&expect, &ret, "abbrev: %d", abbrev); + show(&expect, &ret, "verbose: %d", verbose); + show(&expect, &ret, "quiet: %d", quiet); + show(&expect, &ret, "dry run: %s", dry_run ? "yes" : "no"); + show(&expect, &ret, "file: %s", file ? file : "(not set)"); for (i = 0; i < list.nr; i++) - printf("list: %s\n", list.items[i].string); + show(&expect, &ret, "list: %s", list.items[i].string); for (i = 0; i < argc; i++) - printf("arg %02d: %s\n", i, argv[i]); + show(&expect, &ret, "arg %02d: %s", i, argv[i]); - return 0; + return ret; } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index b8ed96fac6..018a83a5a1 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -103,10 +103,6 @@ Alias /auth/dumb/ www/auth/dumb/ Header set Set-Cookie name=value </LocationMatch> <LocationMatch /smart_headers/> - <RequireAll> - Require expr %{HTTP:x-magic-one} == 'abra' - Require expr %{HTTP:x-magic-two} == 'cadabra' - </RequireAll> SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> @@ -136,6 +132,18 @@ RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302] RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302] +# Apache 2.2 does not understand <RequireAll>, so we use RewriteCond. +# And as RewriteCond does not allow testing for non-matches, we match +# the desired case first (one has abra, two has cadabra), and let it +# pass by marking the RewriteRule as [L], "last rule, do not process +# any other matching RewriteRules after this"), and then have another +# RewriteRule that matches all other cases and lets them fail via '[F]', +# "fail the request". +RewriteCond %{HTTP:x-magic-one} =abra +RewriteCond %{HTTP:x-magic-two} =cadabra +RewriteRule ^/smart_headers/.* - [L] +RewriteRule ^/smart_headers/.* - [F] + <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 79b9074172..60811a3a7c 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -98,7 +98,7 @@ check_sub_test_lib_test () { } check_sub_test_lib_test_err () { - name="$1" # stdin is the expected output output from the test + name="$1" # stdin is the expected output from the test # expected error output is in descriptior 3 ( cd "$name" && diff --git a/t/t0001-init.sh b/t/t0001-init.sh index a5b9e7a4c7..a6fdd5ef3a 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -354,4 +354,34 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' test_path_is_dir realgitdir/refs ' +# Tests for the hidden file attribute on windows +is_hidden () { + # Use the output of `attrib`, ignore the absolute path + case "$(attrib "$1")" in *H*?:*) return 0;; esac + return 1 +} + +test_expect_success MINGW '.git hidden' ' + rm -rf newdir && + ( + unset GIT_DIR GIT_WORK_TREE + mkdir newdir && + cd newdir && + git init && + is_hidden .git + ) && + check_config newdir/.git false unset +' + +test_expect_success MINGW 'bare git dir not hidden' ' + rm -rf newdir && + ( + unset GIT_DIR GIT_WORK_TREE GIT_CONFIG + mkdir newdir && + cd newdir && + git --bare init + ) && + ! is_hidden newdir +' + test_done diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index f33962b178..93725895a4 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -12,7 +12,7 @@ fi compare_files () { tr '\015\000' QN <"$1" >"$1".expect && - tr '\015\000' QN <"$2" >"$2".actual && + tr '\015\000' QN <"$2" | tr -d 'Z' >"$2".actual && test_cmp "$1".expect "$2".actual && rm "$1".expect "$2".actual } @@ -52,14 +52,17 @@ create_gitattributes () { create_NNO_files () { for crlf in false true input do - for attr in "" auto text -text lf crlf + for attr in "" auto text -text do - pfx=NNO_${crlf}_attr_${attr} && - 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 + for aeol in "" lf crlf + do + 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 + done done done } @@ -100,20 +103,22 @@ commit_check_warn () { } commit_chk_wrnNNO () { - crlf=$1 - attr=$2 - lfwarn=$3 - crlfwarn=$4 - lfmixcrlf=$5 - lfmixcr=$6 - crlfnul=$7 - pfx=NNO_${crlf}_attr_${attr} + 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=NNO_attr_${attr}_aeol_${aeol}_${crlf} #Commit files on top of existing file - create_gitattributes "$attr" && + create_gitattributes "$attr" $aeol && for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul do fname=${pfx}_$f.txt && cp $f $fname && + printf Z >>"$fname" && git -c core.autocrlf=$crlf add $fname 2>/dev/null && git -c core.autocrlf=$crlf commit -m "commit_$fname" $fname >"${pfx}_$f.err" 2>&1 done @@ -121,19 +126,19 @@ commit_chk_wrnNNO () { test_expect_success "commit NNO files crlf=$crlf attr=$attr LF" ' check_warning "$lfwarn" ${pfx}_LF.err ' - test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF" ' + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF" ' check_warning "$crlfwarn" ${pfx}_CRLF.err ' - test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF_mix_LF" ' + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF_mix_LF" ' check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err ' - test_expect_success "commit NNO files crlf=$crlf attr=$attr LF_mix_cr" ' + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf LF_mix_cr" ' check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err ' - test_expect_success "commit NNO files crlf=$crlf attr=$attr CRLF_nul" ' + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF_nul" ' check_warning "$crlfnul" ${pfx}_CRLF_nul.err ' } @@ -162,6 +167,7 @@ stats_ascii () { # contruct the attr/ returned by git ls-files --eol # Take none (=empty), one or two args +# convert.c: eol=XX overrides text=auto attr_ascii () { case $1,$2 in -text,*) echo "-text" ;; @@ -169,8 +175,8 @@ attr_ascii () { text,lf) echo "text eol=lf" ;; text,crlf) echo "text eol=crlf" ;; auto,) echo "text=auto" ;; - auto,lf) echo "text=auto eol=lf" ;; - auto,crlf) echo "text=auto eol=crlf" ;; + auto,lf) echo "text eol=lf" ;; + auto,crlf) echo "text eol=crlf" ;; lf,) echo "text eol=lf" ;; crlf,) echo "text eol=crlf" ;; ,) echo "" ;; @@ -195,28 +201,29 @@ check_files_in_repo () { } check_in_repo_NNO () { - crlf=$1 - attr=$2 - lfname=$3 - crlfname=$4 - lfmixcrlf=$5 - lfmixcr=$6 - crlfnul=$7 - pfx=NNO_${crlf}_attr_${attr}_ - test_expect_success "compare_files $lfname ${pfx}LF.txt" ' - compare_files $lfname ${pfx}LF.txt + attr=$1 ; shift + aeol=$1 ; shift + crlf=$1 ; shift + lfname=$1 ; shift + crlfname=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} + test_expect_success "compare_files $lfname ${pfx}_LF.txt" ' + compare_files $lfname ${pfx}_LF.txt ' - test_expect_success "compare_files $crlfname ${pfx}CRLF.txt" ' - compare_files $crlfname ${pfx}CRLF.txt + test_expect_success "compare_files $crlfname ${pfx}_CRLF.txt" ' + compare_files $crlfname ${pfx}_CRLF.txt ' - test_expect_success "compare_files $lfmixcrlf ${pfx}CRLF_mix_LF.txt" ' - compare_files $lfmixcrlf ${pfx}CRLF_mix_LF.txt + test_expect_success "compare_files $lfmixcrlf ${pfx}_CRLF_mix_LF.txt" ' + compare_files $lfmixcrlf ${pfx}_CRLF_mix_LF.txt ' - test_expect_success "compare_files $lfmixcr ${pfx}LF_mix_CR.txt" ' - compare_files $lfmixcr ${pfx}LF_mix_CR.txt + test_expect_success "compare_files $lfmixcr ${pfx}_LF_mix_CR.txt" ' + compare_files $lfmixcr ${pfx}_LF_mix_CR.txt ' - test_expect_success "compare_files $crlfnul ${pfx}CRLF_nul.txt" ' - compare_files $crlfnul ${pfx}CRLF_nul.txt + test_expect_success "compare_files $crlfnul ${pfx}_CRLF_nul.txt" ' + compare_files $crlfnul ${pfx}_CRLF_nul.txt ' } @@ -231,7 +238,7 @@ checkout_files () { lfmixcrlf=$1 ; shift lfmixcr=$1 ; shift crlfnul=$1 ; shift - create_gitattributes "$attr" "$ident" && + create_gitattributes "$attr" $ident $aeol && git config core.autocrlf $crlf && pfx=eol_${ceol}_crlf_${crlf}_attr_${attr}_ && for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul @@ -244,7 +251,7 @@ checkout_files () { fi done - test_expect_success "ls-files --eol attr=$attr $ident $aeol core.autocrlf=$crlf core.eol=$ceol" ' + test_expect_success "ls-files --eol attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol" ' test_when_finished "rm expect actual" && sort <<-EOF >expect && i/crlf w/$(stats_ascii $crlfname) attr/$(attr_ascii $attr $aeol) crlf_false_attr__CRLF.txt @@ -259,19 +266,19 @@ checkout_files () { sort >actual && test_cmp expect actual ' - test_expect_success "checkout $ident $attr $aeol core.autocrlf=$crlf core.eol=$ceol file=LF" " + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF" " compare_ws_file $pfx $lfname crlf_false_attr__LF.txt " - test_expect_success "checkout $ident $attr $aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF" " + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF" " compare_ws_file $pfx $crlfname crlf_false_attr__CRLF.txt " - test_expect_success "checkout $ident $attr $aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF_mix_LF" " + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF_mix_LF" " compare_ws_file $pfx $lfmixcrlf crlf_false_attr__CRLF_mix_LF.txt " - test_expect_success "checkout $ident $attr $aeol core.autocrlf=$crlf core.eol=$ceol file=LF_mix_CR" " + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF_mix_CR" " compare_ws_file $pfx $lfmixcr crlf_false_attr__LF_mix_CR.txt " - test_expect_success "checkout $ident $attr $aeol core.autocrlf=$crlf core.eol=$ceol file=LF_nul" " + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF_nul" " compare_ws_file $pfx $crlfnul crlf_false_attr__LF_nul.txt " } @@ -385,31 +392,31 @@ test_expect_success 'commit files attr=crlf' ' commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" ' -# attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL -commit_chk_wrnNNO false "" "" "" "" "" "" -commit_chk_wrnNNO true "" "LF_CRLF" "" "" "" "" -commit_chk_wrnNNO input "" "" "" "" "" "" - +# attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL +commit_chk_wrnNNO "" "" false "" "" "" "" "" +commit_chk_wrnNNO "" "" true LF_CRLF "" "" "" "" +commit_chk_wrnNNO "" "" input "" "" "" "" "" -commit_chk_wrnNNO false "auto" "$WILC" "$WICL" "$WAMIX" "" "" -commit_chk_wrnNNO true "auto" "LF_CRLF" "" "LF_CRLF" "" "" -commit_chk_wrnNNO input "auto" "" "CRLF_LF" "CRLF_LF" "" "" +commit_chk_wrnNNO "auto" "" false "$WILC" "$WICL" "$WAMIX" "" "" +commit_chk_wrnNNO "auto" "" true LF_CRLF "" LF_CRLF "" "" +commit_chk_wrnNNO "auto" "" input "" CRLF_LF CRLF_LF "" "" -commit_chk_wrnNNO false "text" "$WILC" "$WICL" "$WAMIX" "$WILC" "$WICL" -commit_chk_wrnNNO true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" -commit_chk_wrnNNO input "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" - -commit_chk_wrnNNO false "-text" "" "" "" "" "" -commit_chk_wrnNNO true "-text" "" "" "" "" "" -commit_chk_wrnNNO input "-text" "" "" "" "" "" - -commit_chk_wrnNNO false "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" -commit_chk_wrnNNO true "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" -commit_chk_wrnNNO input "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" +for crlf in true false input +do + commit_chk_wrnNNO -text "" $crlf "" "" "" "" "" + commit_chk_wrnNNO -text lf $crlf "" "" "" "" "" + commit_chk_wrnNNO -text crlf $crlf "" "" "" "" "" + commit_chk_wrnNNO "" lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF + commit_chk_wrnNNO "" crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" + commit_chk_wrnNNO auto lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF + commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" + commit_chk_wrnNNO text lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF + commit_chk_wrnNNO text crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" +done -commit_chk_wrnNNO false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" -commit_chk_wrnNNO true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" -commit_chk_wrnNNO input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" +commit_chk_wrnNNO "text" "" false "$WILC" "$WICL" "$WAMIX" "$WILC" "$WICL" +commit_chk_wrnNNO "text" "" true LF_CRLF "" LF_CRLF LF_CRLF "" +commit_chk_wrnNNO "text" "" input "" CRLF_LF CRLF_LF "" CRLF_LF test_expect_success 'create files cleanup' ' rm -f *.txt && @@ -440,24 +447,20 @@ test_expect_success 'commit -text' ' check_files_in_repo input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul ' -# attr LF CRLF CRLF_mix_LF LF_mix_CR CRLFNUL -check_in_repo_NNO false "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul -check_in_repo_NNO true "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul -check_in_repo_NNO input "" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul - -check_in_repo_NNO false "auto" LF LF LF LF_mix_CR CRLF_nul -check_in_repo_NNO true "auto" LF LF LF LF_mix_CR CRLF_nul -check_in_repo_NNO input "auto" LF LF LF LF_mix_CR CRLF_nul - -check_in_repo_NNO false "text" LF LF LF LF_mix_CR LF_nul -check_in_repo_NNO true "text" LF LF LF LF_mix_CR LF_nul -check_in_repo_NNO input "text" LF LF LF LF_mix_CR LF_nul - -check_in_repo_NNO false "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul -check_in_repo_NNO true "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul -check_in_repo_NNO input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul - - +for crlf in true false input +do + # attr aeol LF CRLF CRLF_mix_LF LF_mix_CR CRLFNUL + check_in_repo_NNO "" "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto "" $crlf LF LF LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto lf $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO auto crlf $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO text "" $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO text lf $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO text crlf $crlf LF LF LF LF_mix_CR LF_nul +done ################################################################################ # Check how files in the repo are changed when they are checked out # How to read the table below: @@ -489,89 +492,47 @@ LFNUL=LF_nul fi export CRLF_MIX_LF_CR MIX NL -checkout_files "" "" "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" false crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" false native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" true crlf CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" true lf CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" "" "" true native CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" false crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" false native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" true crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" true lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" ident "" true native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" false "" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" false crlf CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "auto" "" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" false native $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" "" "" true "" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "auto" "" "" true crlf CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "auto" "" "" true lf CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "auto" "" "" true native CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "auto" ident "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" false crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" false native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" true crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" true lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "auto" ident "" true native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - -for id in "" ident; +# Same handling with and without ident +for id in "" ident do - checkout_files "crlf" "$id" "" false "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" false crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" false lf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" false native CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" input "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" input lf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" true crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" true lf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "crlf" "$id" "" true native CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "lf" "$id" "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" false crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" false native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" true crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" true lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "lf" "$id" "" true native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "text" "$id" "" false "" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL - checkout_files "text" "$id" "" false crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "text" "$id" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "text" "$id" "" false native $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL - checkout_files "text" "$id" "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "text" "$id" "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "text" "$id" "" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "text" "$id" "" true crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "text" "$id" "" true lf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "text" "$id" "" true native CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - checkout_files "-text" "$id" "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" false crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" false native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" input lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" true crlf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" true lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul - checkout_files "-text" "$id" "" true native LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + for ceol in lf crlf native + do + for crlf in true false input + do + # -text overrides core.autocrlf and core.eol + # text and eol=crlf or eol=lf override core.autocrlf and core.eol + checkout_files -text "$id" "" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files -text "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files -text "$id" "crlf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text + checkout_files text "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files text "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # currently the same as text, eol=XXX + checkout_files auto "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + done + + # core.autocrlf false, different core.eol + checkout_files "" "$id" "" false "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # core.autocrlf true + checkout_files "" "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text: core.autocrlf = true overrides core.eol + checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF LF_mix_CR LF_nul + checkout_files text "$id" "" true "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # text: core.autocrlf = input overrides core.eol + checkout_files text "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text=auto + eol=XXX + done + # text: core.autocrlf=false uses core.eol + checkout_files text "$id" "" false crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + checkout_files text "$id" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text: core.autocrlf=false and core.eol unset(or native) uses native eol + checkout_files text "$id" "" false "" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL + checkout_files text "$id" "" false native $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL + # auto: core.autocrlf=false and core.eol unset(or native) uses native eol + checkout_files auto "$id" "" false "" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" false native $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul done # Should be the last test case: remove some files from the worktree diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 9be6411104..db5f60d0c5 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -7,7 +7,7 @@ test_description='our own option parser' . ./test-lib.sh -cat > expect << EOF +cat >expect <<\EOF usage: test-parse-options <options> --yes get a boolean @@ -45,63 +45,24 @@ Standard options -v, --verbose be verbose -n, --dry-run dry run -q, --quiet be quiet + --expect <string> expected output in the variable dump EOF test_expect_success 'test help' ' - test_must_fail test-parse-options -h > output 2> output.err && + test_must_fail test-parse-options -h >output 2>output.err && test_must_be_empty output.err && test_i18ncmp expect output ' mv expect expect.err -cat >expect.template <<EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -EOF - -check() { +check () { what="$1" && shift && expect="$1" && shift && - sed "s/^$what .*/$what $expect/" <expect.template >expect && - test-parse-options $* >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output -} - -check_i18n() { - what="$1" && - shift && - expect="$1" && - shift && - sed "s/^$what .*/$what $expect/" <expect.template >expect && - test-parse-options $* >output 2>output.err && - test_must_be_empty output.err && - test_i18ncmp expect output -} - -check_unknown() { - case "$1" in - --*) - echo error: unknown option \`${1#--}\' >expect ;; - -*) - echo error: unknown switch \`${1#-}\' >expect ;; - esac && - cat expect.err >>expect && - test_must_fail test-parse-options $* >output 2>output.err && - test_must_be_empty output && - test_cmp expect output.err + test-parse-options --expect="$what $expect" "$@" } check_unknown_i18n() { @@ -156,7 +117,7 @@ test_expect_success 'OPT_MAGNITUDE() 3giga' ' check magnitude: 3221225472 -m 3g ' -cat > expect << EOF +cat >expect <<\EOF boolean: 2 integer: 1729 magnitude: 16384 @@ -164,7 +125,7 @@ timestamp: 0 string: 123 abbrev: 7 verbose: 2 -quiet: no +quiet: 0 dry run: yes file: prefix/my.file EOF @@ -176,7 +137,7 @@ test_expect_success 'short options' ' test_must_be_empty output.err ' -cat > expect << EOF +cat >expect <<\EOF boolean: 2 integer: 1729 magnitude: 16384 @@ -184,7 +145,7 @@ timestamp: 0 string: 321 abbrev: 10 verbose: 2 -quiet: no +quiet: 0 dry run: no file: prefix/fi.le EOF @@ -204,15 +165,15 @@ test_expect_success 'missing required value' ' test_expect_code 129 test-parse-options --file ' -cat > expect << EOF +cat >expect <<\EOF boolean: 1 integer: 13 magnitude: 0 timestamp: 0 string: 123 abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) arg 00: a1 @@ -222,32 +183,32 @@ EOF test_expect_success 'intermingled arguments' ' test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ - > output 2> output.err && + >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect << EOF +cat >expect <<\EOF boolean: 0 integer: 2 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'unambiguously abbreviated option' ' - test-parse-options --int 2 --boolean --no-bo > output 2> output.err && + test-parse-options --int 2 --boolean --no-bo >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'unambiguously abbreviated option with "="' ' - test-parse-options --int=2 > output 2> output.err && + test-parse-options --int=2 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' @@ -256,74 +217,74 @@ test_expect_success 'ambiguously abbreviated option' ' test_expect_code 129 test-parse-options --strin 123 ' -cat > expect << EOF +cat >expect <<\EOF boolean: 0 integer: 0 magnitude: 0 timestamp: 0 string: 123 abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'non ambiguous option (after two options it abbreviates)' ' - test-parse-options --st 123 > output 2> output.err && + test-parse-options --st 123 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > typo.err << EOF -error: did you mean \`--boolean\` (with two dashes ?) +cat >typo.err <<\EOF +error: did you mean `--boolean` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -boolean > output 2> output.err && + test_must_fail test-parse-options -boolean >output 2>output.err && test_must_be_empty output && test_cmp typo.err output.err ' -cat > typo.err << EOF -error: did you mean \`--ambiguous\` (with two dashes ?) +cat >typo.err <<\EOF +error: did you mean `--ambiguous` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -ambiguous > output 2> output.err && + test_must_fail test-parse-options -ambiguous >output 2>output.err && test_must_be_empty output && test_cmp typo.err output.err ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 0 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) arg 00: --quux EOF test_expect_success 'keep some options as arguments' ' - test-parse-options --quux > output 2> output.err && + test-parse-options --quux >output 2>output.err && test_must_be_empty output.err && - test_cmp expect output + test_cmp expect output ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 0 magnitude: 0 timestamp: 1 string: (not set) abbrev: 7 -verbose: 0 -quiet: yes +verbose: -1 +quiet: 1 dry run: no file: (not set) arg 00: foo @@ -331,12 +292,12 @@ EOF test_expect_success 'OPT_DATE() works' ' test-parse-options -t "1970-01-01 00:00:01 +0000" \ - foo -q > output 2> output.err && + foo -q >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF +cat >expect <<\EOF Callback: "four", 0 boolean: 5 integer: 4 @@ -344,112 +305,110 @@ magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' - test-parse-options --length=four -b -4 > output 2> output.err && + test-parse-options --length=four -b -4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF -Callback: "not set", 1 -EOF +>expect test_expect_success 'OPT_CALLBACK() and callback errors work' ' - test_must_fail test-parse-options --no-length > output 2> output.err && + test_must_fail test-parse-options --no-length >output 2>output.err && test_i18ncmp expect output && test_i18ncmp expect.err output.err ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 1 integer: 23 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err && + test-parse-options --set23 -bbbbb --no-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err && + test-parse-options --set23 -bbbbb --neg-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 6 integer: 0 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_BIT() works' ' - test-parse-options -bb --or4 > output 2> output.err && + test-parse-options -bb --or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() works' ' - test-parse-options -bb --no-neg-or4 > output 2> output.err && + test-parse-options -bb --no-neg-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' - test-parse-options + + + + + + > output 2> output.err && + test-parse-options + + + + + + >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 12345 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_NUMBER_CALLBACK() works' ' - test-parse-options -12345 > output 2> output.err && + test-parse-options -12345 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat >expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 0 magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF @@ -460,7 +419,7 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' test_cmp expect output ' -cat >>expect <<'EOF' +cat >>expect <<\EOF list: foo list: bar list: baz @@ -476,4 +435,118 @@ test_expect_success '--no-list resets list' ' test_cmp expect output ' +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: -1 +quiet: 3 +dry run: no +file: (not set) +EOF + +test_expect_success 'multiple quiet levels' ' + test-parse-options -q -q -q >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 3 +quiet: 0 +dry run: no +file: (not set) +EOF + +test_expect_success 'multiple verbose levels' ' + test-parse-options -v -v -v >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: -1 +quiet: 0 +dry run: no +file: (not set) +EOF + +test_expect_success '--no-quiet sets --quiet to 0' ' + test-parse-options --no-quiet >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: -1 +quiet: 0 +dry run: no +file: (not set) +EOF + +test_expect_success '--no-quiet resets multiple -q to 0' ' + test-parse-options -q -q -q --no-quiet >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 0 +quiet: 0 +dry run: no +file: (not set) +EOF + +test_expect_success '--no-verbose sets verbose to 0' ' + test-parse-options --no-verbose >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + +cat >expect <<\EOF +boolean: 0 +integer: 0 +magnitude: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 0 +quiet: 0 +dry run: no +file: (not set) +EOF + +test_expect_success '--no-verbose resets multiple verbose to 0' ' + test-parse-options -v -v -v --no-verbose >output 2>output.err && + test_must_be_empty output.err && + test_cmp expect output +' + test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 8532a028e7..bf2deee109 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -19,6 +19,13 @@ relative_path() { "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'" } +test_submodule_relative_url() { + test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" " + actual=\$(git submodule--helper resolve-relative-url-test '$1' '$2' '$3') && + test \"\$actual\" = '$4' + " +} + test_git_path() { test_expect_success "git-path $1 $2 => $3" " $1 git rev-parse --git-path $2 >actual && @@ -298,4 +305,43 @@ test_git_path GIT_COMMON_DIR=bar config bar/config test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs test_git_path GIT_COMMON_DIR=bar shallow bar/shallow +# In the tests below, the distinction between $PWD and $(pwd) is important: +# on Windows, $PWD is POSIX style (/c/foo), $(pwd) has drive letter (c:/foo). + +test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule" +test_submodule_relative_url "../" "../foo/bar" "../submodule" "../../foo/submodule" +test_submodule_relative_url "../" "../foo/submodule" "../submodule" "../../foo/submodule" +test_submodule_relative_url "../" "./foo" "../submodule" "../submodule" +test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "../../../" "../foo/bar" "../sub/a/b/c" "../../../../foo/sub/a/b/c" +test_submodule_relative_url "../" "$PWD/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "../" "foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "../" "foo" "../submodule" "../submodule" + +test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" "../foo/submodule" +test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule" +test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule" +test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule" +test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo" +test_submodule_relative_url "(null)" "$PWD/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r" +test_submodule_relative_url "(null)" "$PWD/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r" +test_submodule_relative_url "(null)" "$PWD/." "../." "$(pwd)/." +test_submodule_relative_url "(null)" "$PWD" "./." "$(pwd)/." +test_submodule_relative_url "(null)" "$PWD/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "(null)" "$PWD" "./å äö" "$(pwd)/å äö" +test_submodule_relative_url "(null)" "$PWD/." "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$PWD/submodule" "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$PWD/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1" +test_submodule_relative_url "(null)" "$PWD/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/." +test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tmp/subrepo" +test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule" +test_submodule_relative_url "(null)" "foo" "../submodule" "submodule" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../subrepo" "helper:://hostname/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../subrepo" "ssh://hostname/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh://hostname:22/subrepo" +test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo" +test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo" + test_done diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh new file mode 100755 index 0000000000..5e3fb3a6af --- /dev/null +++ b/t/t1350-config-hooks-path.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='Test the core.hooksPath configuration variable' + +. ./test-lib.sh + +test_expect_success 'set up a pre-commit hook in core.hooksPath' ' + mkdir -p .git/custom-hooks .git/hooks && + write_script .git/custom-hooks/pre-commit <<-\EOF && + echo CUSTOM >>actual + EOF + write_script .git/hooks/pre-commit <<-\EOF + echo NORMAL >>actual + EOF +' + +test_expect_success 'Check that various forms of specifying core.hooksPath work' ' + test_commit no_custom_hook && + git config core.hooksPath .git/custom-hooks && + test_commit have_custom_hook && + git config core.hooksPath .git/custom-hooks/ && + test_commit have_custom_hook_trailing_slash && + git config core.hooksPath "$PWD/.git/custom-hooks" && + test_commit have_custom_hook_abs_path && + git config core.hooksPath "$PWD/.git/custom-hooks/" && + test_commit have_custom_hook_abs_path_trailing_slash && + cat >expect <<-\EOF && + NORMAL + CUSTOM + CUSTOM + CUSTOM + CUSTOM + EOF + test_cmp expect actual +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index e66b7cb697..7ee8ea004f 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -427,6 +427,24 @@ test_expect_success 'fsck allows .Ňit' ' ) ' +test_expect_success 'NUL in commit' ' + rm -fr nul-in-commit && + git init nul-in-commit && + ( + cd nul-in-commit && + git commit --allow-empty -m "initial commitQNUL after message" && + git cat-file commit HEAD >original && + q_to_nul <original >munged && + git hash-object -w -t commit --stdin <munged >name && + git branch bad $(cat name) && + + test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 && + grep nulInCommit warn.1 && + git fsck 2>warn.2 && + grep nulInCommit warn.2 + ) +' + # create a static test repo which is broken by omitting # one particular object ($1, which is looked up via rev-parse # in the new repository). diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 3acb9926f2..3a22fc55fc 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -4,6 +4,8 @@ test_description='test git worktree add' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + test_expect_success 'setup' ' test_commit init ' @@ -225,4 +227,61 @@ test_expect_success '"add" worktree with --checkout' ' test_cmp init.t swamp2/init.t ' +test_expect_success 'put a worktree under rebase' ' + git worktree add under-rebase && + ( + cd under-rebase && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + git worktree list | grep "under-rebase.*detached HEAD" + ) +' + +test_expect_success 'add a worktree, checking out a rebased branch' ' + test_must_fail git worktree add new-rebase under-rebase && + ! test -d new-rebase +' + +test_expect_success 'checking out a rebased branch from another worktree' ' + git worktree add new-place && + test_must_fail git -C new-place checkout under-rebase +' + +test_expect_success 'not allow to delete a branch under rebase' ' + ( + cd under-rebase && + test_must_fail git branch -D under-rebase + ) +' + +test_expect_success 'rename a branch under rebase not allowed' ' + test_must_fail git branch -M under-rebase rebase-with-new-name +' + +test_expect_success 'check out from current worktree branch ok' ' + ( + cd under-rebase && + git checkout under-rebase && + git checkout - && + git rebase --abort + ) +' + +test_expect_success 'checkout a branch under bisect' ' + git worktree add under-bisect && + ( + cd under-bisect && + git bisect start && + git bisect bad && + git bisect good HEAD~2 && + git worktree list | grep "under-bisect.*detached HEAD" && + test_must_fail git worktree add new-bisect under-bisect && + ! test -d new-bisect + ) +' + +test_expect_success 'rename a branch under bisect not allowed' ' + test_must_fail git branch -M under-bisect bisect-with-new-name +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index d96d0e4c9b..66348f11d1 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -62,7 +62,7 @@ test_expect_success 'setup' ' # "exec" commands are ran with the user shell by default, but this may # be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work -# to create a file. Unseting SHELL avoids such non-portable behavior +# to create a file. Unsetting SHELL avoids such non-portable behavior # in tests. It must be exported for it to take effect where needed. SHELL= export SHELL diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh index a1c4e0216f..db9378142a 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -14,11 +14,11 @@ test_description='revert can handle submodules' git_revert () { git status -su >expect && ls -1pR * >>expect && - tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + tar cf "$TRASH_DIRECTORY/tmp.tar" * && git checkout "$1" && git revert HEAD && rm -rf * && - tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + tar xf "$TRASH_DIRECTORY/tmp.tar" && git status -su >actual && ls -1pR * >>actual && test_cmp expect actual && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index eed2981b96..8049cad374 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1460,4 +1460,109 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' ' test_path_is_dir patchset ' +test_expect_success 'format-patch --base' ' + git checkout side && + git format-patch --stdout --base=HEAD~3 -1 >patch && + grep "^base-commit:" patch >actual && + grep "^prerequisite-patch-id:" patch >>actual && + echo "base-commit: $(git rev-parse HEAD~3)" >expected && + echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected && + echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base errors out when base commit is in revision list' ' + test_must_fail git format-patch --base=HEAD -2 && + test_must_fail git format-patch --base=HEAD~1 -2 && + git format-patch --stdout --base=HEAD~2 -2 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse HEAD~2)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base errors out when base commit is not ancestor of revision list' ' + # For history as below: + # + # ---Q---P---Z---Y---*---X + # \ / + # ------------W + # + # If "format-patch Z..X" is given, P and Z can not be specified as the base commit + git checkout -b topic1 master && + git rev-parse HEAD >commit-id-base && + test_commit P && + git rev-parse HEAD >commit-id-P && + test_commit Z && + git rev-parse HEAD >commit-id-Z && + test_commit Y && + git checkout -b topic2 master && + test_commit W && + git merge topic1 && + test_commit X && + test_must_fail git format-patch --base=$(cat commit-id-P) -3 && + test_must_fail git format-patch --base=$(cat commit-id-Z) -3 && + git format-patch --stdout --base=$(cat commit-id-base) -3 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(cat commit-id-base)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base=auto' ' + git checkout -b upstream master && + git checkout -b local upstream && + git branch --set-upstream-to=upstream && + test_commit N1 && + test_commit N2 && + git format-patch --stdout --base=auto -2 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse upstream)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch errors out when history involves criss-cross' ' + # setup criss-cross history + # + # B---M1---D + # / \ / + # A X + # \ / \ + # C---M2---E + # + git checkout master && + test_commit A && + git checkout -b xb master && + test_commit B && + git checkout -b xc master && + test_commit C && + git checkout -b xbc xb -- && + git merge xc && + git checkout -b xcb xc -- && + git branch --set-upstream-to=xbc && + git merge xb && + git checkout xbc && + test_commit D && + git checkout xcb && + test_commit E && + test_must_fail git format-patch --base=auto -1 +' + +test_expect_success 'format-patch format.useAutoBaseoption' ' + test_when_finished "git config --unset format.useAutoBase" && + git checkout local && + git config format.useAutoBase true && + git format-patch --stdout -1 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse upstream)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base overrides format.useAutoBase' ' + test_when_finished "git config --unset format.useAutoBase" && + git config format.useAutoBase true && + git format-patch --stdout --base=HEAD~1 -1 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse HEAD~1)" >expected && + test_cmp expected actual +' + test_done diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index ea5ace99a1..9473c2779e 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -82,7 +82,7 @@ test_expect_success 'am -3 --abort removes otherfile-4' ' test 4 = "$(cat otherfile-4)" && git am --abort && test_cmp_rev initial HEAD && - test -z $(git ls-files -u) && + test -z "$(git ls-files -u)" && test_path_is_missing otherfile-4 ' diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 48e2ab62da..3484b6f0f3 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -91,23 +91,55 @@ test_expect_success 'configured username does not override URL' ' expect_askpass pass user@host ' -test_expect_success 'cmdline credential config passes into submodules' ' +test_expect_success 'set up repo with http submodules' ' git init super && set_askpass user@host pass@host && ( cd super && git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub && git commit -m "add submodule" - ) && + ) +' + +test_expect_success 'cmdline credential config passes to submodule via clone' ' set_askpass wrong pass@host && test_must_fail git clone --recursive super super-clone && rm -rf super-clone && + set_askpass wrong pass@host && - git -c "credential.$HTTP_URL.username=user@host" \ + git -c "credential.$HTTPD_URL.username=user@host" \ clone --recursive super super-clone && expect_askpass pass user@host ' +test_expect_success 'cmdline credential config passes submodule via fetch' ' + set_askpass wrong pass@host && + test_must_fail git -C super-clone fetch --recurse-submodules && + + set_askpass wrong pass@host && + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + fetch --recurse-submodules && + expect_askpass pass user@host +' + +test_expect_success 'cmdline credential config passes submodule update' ' + # advance the submodule HEAD so that a fetch is required + git commit --allow-empty -m foo && + git push "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git" HEAD && + sha1=$(git rev-parse HEAD) && + git -C super-clone update-index --cacheinfo 160000,$sha1,sub && + + set_askpass wrong pass@host && + test_must_fail git -C super-clone submodule update && + + set_askpass wrong pass@host && + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + submodule update && + expect_askpass pass user@host +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two && diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index e44fe72c7a..2f375eb94d 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -283,10 +283,20 @@ test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' ' ' test_expect_success 'custom http headers' ' - test_must_fail git fetch "$HTTPD_URL/smart_headers/repo.git" && + test_must_fail git -c http.extraheader="x-magic-two: cadabra" \ + fetch "$HTTPD_URL/smart_headers/repo.git" && git -c http.extraheader="x-magic-one: abra" \ -c http.extraheader="x-magic-two: cadabra" \ - fetch "$HTTPD_URL/smart_headers/repo.git" + fetch "$HTTPD_URL/smart_headers/repo.git" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + git config -f .gitmodules submodule.sub.path sub && + git config -f .gitmodules submodule.sub.url \ + "$HTTPD_URL/smart_headers/repo.git" && + git submodule init sub && + test_must_fail git submodule update sub && + git -c http.extraheader="x-magic-one: abra" \ + -c http.extraheader="x-magic-two: cadabra" \ + submodule update sub ' stop_httpd diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 150aeaf713..a433394200 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -466,7 +466,7 @@ test_expect_success 'clone ssh://host.xz:22/~repo' ' #IPv6 for tuah in ::1 [::1] [::1]: user@::1 user@[::1] user@[::1]: [user@::1] [user@::1]: do - ehost=$(echo $tuah | sed -e "s/1]:/1]/ "| tr -d "[]") + ehost=$(echo $tuah | sed -e "s/1]:/1]/" | tr -d "[]") test_expect_success "clone ssh://$tuah/home/user/repo" " test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo " diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh index 27d730c0a7..e4850b778c 100755 --- a/t/t5611-clone-config.sh +++ b/t/t5611-clone-config.sh @@ -37,4 +37,24 @@ test_expect_success 'clone -c config is available during clone' ' test_cmp expect child/file ' +# Tests for the hidden file attribute on windows +is_hidden () { + # Use the output of `attrib`, ignore the absolute path + case "$(attrib "$1")" in *H*?:*) return 0;; esac + return 1 +} + +test_expect_success MINGW 'clone -c core.hideDotFiles' ' + test_commit attributes .gitattributes "" && + rm -rf child && + git clone -c core.hideDotFiles=false . child && + ! is_hidden child/.gitattributes && + rm -rf child && + git clone -c core.hideDotFiles=dotGitOnly . child && + ! is_hidden child/.gitattributes && + rm -rf child && + git clone -c core.hideDotFiles=true . child && + is_hidden child/.gitattributes +' + test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 755d30ce2a..3f59e58dfb 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -76,7 +76,7 @@ test_expect_success "result contains a conflict" "test_cmp expect a1" git ls-files --stage > out cat > expect << EOF -100644 439cc46de773d8a83c77799b7cc9191c128bfcff 1 a1 +100644 ec3fe2a791706733f2d8fa7ad45d9a9672031f5e 1 a1 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 EOF diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 9d6621c056..18aa88b5c0 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -212,7 +212,8 @@ test_expect_success 'git detects differently handled merges conflict' ' -L "" \ -L "Temporary merge branch 1" \ merged empty merge-me && - test $(git rev-parse :1:new_a) = $(git hash-object merged) + sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + test $(git rev-parse :1:new_a) = $(git hash-object merged-internal) ' # @@ -299,89 +300,6 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev ' # -# criss-cross + modify/modify with very contrived file contents: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: file with contents 'A\n' -# Commit B: file with contents 'B\n' -# Commit C: file with contents 'C\n' -# Commit D: file with contents 'D\n' -# Commit E: file with contents: -# <<<<<<< Temporary merge branch 1 -# C -# ======= -# B -# >>>>>>> Temporary merge branch 2 -# -# Now, when we merge commits D & E, does git detect the conflict? - -test_expect_success 'setup differently handled merges of content conflict' ' - git clean -fdqx && - rm -rf .git && - git init && - - echo A >file && - git add file && - test_tick && - git commit -m A && - - git branch B && - git checkout -b C && - echo C >file && - git add file && - test_tick && - git commit -m C && - - git checkout B && - echo B >file && - git add file && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - echo D >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - cat <<EOF >file && -<<<<<<< Temporary merge branch 1 -C -======= -B ->>>>>>> Temporary merge branch 2 -EOF - git add file && - test_tick && - git commit -m E && - git tag E -' - -test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' ' - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:file) = $(git rev-parse D:file) && - test $(git rev-parse :3:file) = $(git rev-parse E:file) -' - -# # criss-cross + d/f conflict via add/add: # Commit A: Neither file 'a' nor directory 'a/' exists. # Commit B: Introduce 'a' diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh index c6b7aa6977..62b8a2e7bb 100755 --- a/t/t6041-bisect-submodule.sh +++ b/t/t6041-bisect-submodule.sh @@ -8,7 +8,7 @@ test_description='bisect can handle submodules' git_bisect () { git status -su >expect && ls -1pR * >>expect && - tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + tar cf "$TRASH_DIRECTORY/tmp.tar" * && GOOD=$(git rev-parse --verify HEAD) && git checkout "$1" && echo "foo" >bar && @@ -20,7 +20,7 @@ git_bisect () { git bisect start && git bisect good $GOOD && rm -rf * && - tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + tar xf "$TRASH_DIRECTORY/tmp.tar" && git status -su >actual && ls -1pR * >>actual && test_cmp expect actual && diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 70afb44271..d0ab09f4bd 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -5,15 +5,6 @@ test_description='test for-each-refs usage of ref-filter APIs' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh -test_prepare_expect () { - if test_have_prereq GPG - then - cat - else - sed '/signed/d' - fi -} - test_expect_success 'setup some history and refs' ' test_commit one && test_commit two && @@ -22,11 +13,19 @@ test_expect_success 'setup some history and refs' ' test_commit four && git tag -m "An annotated tag" annotated-tag && git tag -m "Annonated doubly" doubly-annotated-tag annotated-tag && + + # Note that these "signed" tags might not actually be signed. + # Tests which care about the distinction should be marked + # with the GPG prereq. if test_have_prereq GPG then - git tag -s -m "A signed tag" signed-tag && - git tag -s -m "Signed doubly" doubly-signed-tag signed-tag + sign=-s + else + sign= fi && + git tag $sign -m "A signed tag" signed-tag && + git tag $sign -m "Signed doubly" doubly-signed-tag signed-tag && + git checkout master && git update-ref refs/odd/spot master ' @@ -42,7 +41,7 @@ test_expect_success 'filtering with --points-at' ' ' test_expect_success 'check signed tags with --points-at' ' - test_prepare_expect <<-\EOF | sed -e "s/Z$//" >expect && + sed -e "s/Z$//" >expect <<-\EOF && refs/heads/side Z refs/tags/annotated-tag four refs/tags/four Z @@ -65,7 +64,7 @@ test_expect_success 'filtering with --merged' ' ' test_expect_success 'filtering with --no-merged' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && refs/heads/side refs/tags/annotated-tag refs/tags/doubly-annotated-tag @@ -78,7 +77,7 @@ test_expect_success 'filtering with --no-merged' ' ' test_expect_success 'filtering with --contains' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && refs/heads/master refs/heads/side refs/odd/spot @@ -99,7 +98,7 @@ test_expect_success '%(color) must fail' ' ' test_expect_success 'left alignment is default' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && refname is refs/heads/master |refs/heads/master refname is refs/heads/side |refs/heads/side refname is refs/odd/spot |refs/odd/spot @@ -117,7 +116,7 @@ test_expect_success 'left alignment is default' ' ' test_expect_success 'middle alignment' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot @@ -135,7 +134,7 @@ test_expect_success 'middle alignment' ' ' test_expect_success 'right alignment' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && | refname is refs/heads/master|refs/heads/master | refname is refs/heads/side|refs/heads/side | refname is refs/odd/spot|refs/odd/spot @@ -152,7 +151,7 @@ test_expect_success 'right alignment' ' test_cmp expect actual ' -test_prepare_expect >expect <<-\EOF +cat >expect <<-\EOF | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot @@ -199,7 +198,7 @@ EOF # Individual atoms inside %(align:...) and %(end) must not be quoted. test_expect_success 'alignment with format quote' " - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && |' '\''master| A U Thor'\'' '| |' '\''side| A U Thor'\'' '| |' '\''odd/spot| A U Thor'\'' '| @@ -217,7 +216,7 @@ test_expect_success 'alignment with format quote' " " test_expect_success 'nested alignment with quote formatting' " - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && |' master '| |' side '| |' odd/spot '| @@ -235,7 +234,7 @@ test_expect_success 'nested alignment with quote formatting' " " test_expect_success 'check `%(contents:lines=1)`' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && master |three side |four odd/spot |three @@ -253,7 +252,7 @@ test_expect_success 'check `%(contents:lines=1)`' ' ' test_expect_success 'check `%(contents:lines=0)`' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && master | side | odd/spot | @@ -271,7 +270,7 @@ test_expect_success 'check `%(contents:lines=0)`' ' ' test_expect_success 'check `%(contents:lines=99999)`' ' - test_prepare_expect >expect <<-\EOF && + cat >expect <<-\EOF && master |three side |four odd/spot |three diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index f99f674ac7..3570f7bb8c 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -11,6 +11,10 @@ subcommands of git submodule. . ./test-lib.sh +test_expect_success 'submodule deinit works on empty repository' ' + git submodule deinit --all +' + test_expect_success 'setup - initial commit' ' >t && git add t && @@ -18,6 +22,22 @@ test_expect_success 'setup - initial commit' ' git branch initial ' +test_expect_success 'submodule init aborts on missing .gitmodules file' ' + test_when_finished "git update-index --remove sub" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + # missing the .gitmodules file here + test_must_fail git submodule init 2>actual && + test_i18ngrep "No url found for submodule path" actual +' + +test_expect_success 'submodule update aborts on missing .gitmodules file' ' + test_when_finished "git update-index --remove sub" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + # missing the .gitmodules file here + git submodule update sub 2>actual && + test_i18ngrep "Submodule path .sub. not initialized" actual +' + test_expect_success 'configuration parsing' ' test_when_finished "rm -f .gitmodules" && cat >.gitmodules <<-\EOF && @@ -898,8 +918,9 @@ test_expect_success 'submodule deinit works on repository without submodules' ' git init && >file && git add file && - git commit -m "repo should not be empty" - git submodule deinit . + git commit -m "repo should not be empty" && + git submodule deinit . && + git submodule deinit --all ) ' @@ -941,6 +962,19 @@ test_expect_success 'submodule deinit . deinits all initialized submodules' ' rmdir init example2 ' +test_expect_success 'submodule deinit --all deinits all initialized submodules' ' + git submodule update --init && + git config submodule.example.foo bar && + git config submodule.example2.frotz nitfol && + test_must_fail git submodule deinit && + git submodule deinit --all >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -z "$(git config --get-regexp "submodule\.example2\.")" && + test_i18ngrep "Cleared directory .init" actual && + test_i18ngrep "Cleared directory .example2" actual && + rmdir init example2 +' + test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' ' git submodule update --init && rm -rf init example2/* example2/.git && @@ -1007,6 +1041,10 @@ test_expect_success 'submodule deinit is silent when used on an uninitialized su test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual && test_i18ngrep "Cleared directory .init" actual && + git submodule deinit --all >actual && + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual && + test_i18ngrep "Cleared directory .init" actual && rmdir init example2 ' diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index fd741f506f..5f278799d5 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -108,24 +108,36 @@ pwd=$(pwd) cat <<EOF >expect Submodule path '../super': checked out '$supersha1' -Submodule 'merging' ($pwd/merging) registered for path '../super/merging' -Submodule 'none' ($pwd/none) registered for path '../super/none' -Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing' -Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule' Submodule path '../super/merging': checked out '$mergingsha1' Submodule path '../super/none': checked out '$nonesha1' Submodule path '../super/rebasing': checked out '$rebasingsha1' Submodule path '../super/submodule': checked out '$submodulesha1' EOF +cat <<EOF >expect2 +Submodule 'merging' ($pwd/merging) registered for path '../super/merging' +Submodule 'none' ($pwd/none) registered for path '../super/none' +Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing' +Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule' +Cloning into '$pwd/recursivesuper/super/merging'... +done. +Cloning into '$pwd/recursivesuper/super/none'... +done. +Cloning into '$pwd/recursivesuper/super/rebasing'... +done. +Cloning into '$pwd/recursivesuper/super/submodule'... +done. +EOF + test_expect_success 'submodule update --init --recursive from subdirectory' ' git -C recursivesuper/super reset --hard HEAD^ && (cd recursivesuper && mkdir tmp && cd tmp && - git submodule update --init --recursive ../super >../../actual + git submodule update --init --recursive ../super >../../actual 2>../../actual2 ) && - test_cmp expect actual + test_cmp expect actual && + test_cmp expect2 actual2 ' apos="'"; diff --git a/t/t7412-submodule--helper.sh b/t/t7412-submodule--helper.sh deleted file mode 100755 index 149d42864f..0000000000 --- a/t/t7412-submodule--helper.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2016 Jacob Keller -# - -test_description='Basic plumbing support of submodule--helper - -This test verifies the submodule--helper plumbing command used to implement -git-submodule. -' - -. ./test-lib.sh - -test_expect_success 'sanitize-config clears configuration' ' - git -c user.name="Some User" submodule--helper sanitize-config >actual && - test_must_be_empty actual -' - -sq="'" -test_expect_success 'sanitize-config keeps credential.helper' ' - git -c credential.helper=helper submodule--helper sanitize-config >actual && - echo "${sq}credential.helper=helper${sq}" >expect && - test_cmp expect actual -' - -test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 900f7de05a..d84897a67a 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -607,4 +607,24 @@ test_expect_success '--only works on to-be-born branch' ' test_cmp expected actual ' +test_expect_success '--dry-run with conflicts fixed from a merge' ' + # setup two branches with conflicting information + # in the same file, resolve the conflict, + # call commit with --dry-run + echo "Initial contents, unimportant" >test-file && + git add test-file && + git commit -m "Initial commit" && + echo "commit-1-state" >test-file && + git commit -m "commit 1" -i test-file && + git tag commit-1 && + git checkout -b branch-2 HEAD^1 && + echo "commit-2-state" >test-file && + git commit -m "commit 2" -i test-file && + ! $(git merge --no-commit commit-1) && + echo "commit-2-state" >test-file && + git add test-file && + git commit --dry-run && + git commit -m "conflicts fixed from merge." +' + test_done diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh index 2ddf28c984..ed2653d46f 100755 --- a/t/t7507-commit-verbose.sh +++ b/t/t7507-commit-verbose.sh @@ -3,11 +3,10 @@ test_description='verbose commit template' . ./test-lib.sh -cat >check-for-diff <<EOF -#!$SHELL_PATH -exec grep '^diff --git' "\$1" +write_script "check-for-diff" <<\EOF && +grep '^diff --git' "$1" >out +exit 0 EOF -chmod +x check-for-diff test_set_editor "$PWD/check-for-diff" cat >message <<'EOF' @@ -23,7 +22,8 @@ test_expect_success 'setup' ' ' test_expect_success 'initial commit shows verbose diff' ' - git commit --amend -v + git commit --amend -v && + test_line_count = 1 out ' test_expect_success 'second commit' ' @@ -39,13 +39,15 @@ check_message() { test_expect_success 'verbose diff is stripped out' ' git commit --amend -v && - check_message message + check_message message && + test_line_count = 1 out ' test_expect_success 'verbose diff is stripped out (mnemonicprefix)' ' git config diff.mnemonicprefix true && git commit --amend -v && - check_message message + check_message message && + test_line_count = 1 out ' cat >diff <<'EOF' @@ -96,4 +98,60 @@ test_expect_success 'verbose diff is stripped out with set core.commentChar' ' test_i18ngrep "Aborting commit due to empty commit message." err ' +test_expect_success 'status does not verbose without --verbose' ' + git status >actual && + ! grep "^diff --git" actual +' + +test_expect_success 'setup -v -v' ' + echo dirty >file +' + +for i in true 1 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 1 out + " +done + +for i in false -2 -1 0 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 0 out + " +done + +for i in 2 3 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 2 out + " +done + +for i in true false -2 -1 0 1 2 3 +do + test_expect_success "commit.verbose=$i and --verbose" " + git -c commit.verbose=$i commit --amend --verbose && + test_line_count = 1 out + " + + test_expect_success "commit.verbose=$i and --no-verbose" " + git -c commit.verbose=$i commit --amend --no-verbose && + test_line_count = 0 out + " + + test_expect_success "commit.verbose=$i and -v -v" " + git -c commit.verbose=$i commit --amend -v -v && + test_line_count = 2 out + " +done + +test_expect_success "status ignores commit.verbose=true" ' + git -c commit.verbose=true status >actual && + ! grep "^diff --git actual" +' + test_done diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 18e5cf0663..4177a8609a 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -45,12 +45,18 @@ test_expect_success GPG 'create signed commits' ' git tag seventh-signed && echo 8 >file && test_tick && git commit -a -m eighth -SB7227189 && - git tag eighth-signed-alt + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) && + # explicit -S of course must sign. + git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree}) ' test_expect_success GPG 'verify and show signatures' ' ( - for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed do git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && @@ -60,7 +66,8 @@ test_expect_success GPG 'verify and show signatures' ' done ) && ( - for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned do test_must_fail git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index 0e4a682c64..6729cb379f 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -37,14 +37,14 @@ EOF test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' test_must_fail git merge branch 2>out && - test_cmp out expect && + test_i18ncmp out expect && git commit --allow-empty -m empty && ( GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY && test_must_fail git merge branch 2>out2 ) && - test_cmp out2 expect && + test_i18ncmp out2 expect && git reset --hard HEAD^ ' @@ -53,7 +53,7 @@ error: Your local changes to the following files would be overwritten by merge: four three two -Please, commit your changes or stash them before you can merge. +Please commit your changes or stash them before you can merge. error: The following untracked working tree files would be overwritten by merge: five Please move or remove them before you can merge. @@ -65,14 +65,14 @@ test_expect_success 'untracked files or local changes ovewritten by merge' ' git add three && git add four && test_must_fail git merge branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: rep/one rep/two -Please, commit your changes or stash them before you can switch branches. +Please commit your changes or stash them before you can switch branches. Aborting EOF @@ -87,21 +87,21 @@ test_expect_success 'cannot switch branches because of local changes' ' echo uno >rep/one && echo dos >rep/two && test_must_fail git checkout branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: rep/one rep/two -Please, commit your changes or stash them before you can switch branches. +Please commit your changes or stash them before you can switch branches. Aborting EOF test_expect_success 'not uptodate file porcelain checkout error' ' git add rep/one rep/two && test_must_fail git checkout branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF @@ -132,7 +132,7 @@ test_expect_success 'not_uptodate_dir porcelain checkout error' ' >rep/untracked-file && >rep2/untracked-file && test_must_fail git checkout branch 2>out && - test_cmp out ../expect + test_i18ncmp out ../expect ' test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 8d99eb303f..3978fc0b45 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -718,20 +718,13 @@ test_cmp_rev () { test_cmp expect.rev actual.rev } -# Print a sequence of numbers or letters in increasing order. This is -# similar to GNU seq(1), but the latter might not be available -# everywhere (and does not do letters). It may be used like: -# -# for i in $(test_seq 100) -# do -# for j in $(test_seq 10 20) -# do -# for k in $(test_seq a z) -# do -# echo $i-$j-$k -# done -# done -# done +# Print a sequence of integers in increasing order, either with +# two arguments (start and end): +# +# test_seq 1 5 -- outputs 1 2 3 4 5 one line at a time +# +# or with one argument (end), in which case it starts counting +# from 1. test_seq () { case $# in @@ -739,7 +732,12 @@ test_seq () { 2) ;; *) error "bug in the test script: not 1 or 2 parameters to test_seq" ;; esac - perl -le 'print for $ARGV[0]..$ARGV[1]' -- "$@" + test_seq_counter__=$1 + while test "$test_seq_counter__" -le "$2" + do + echo "$test_seq_counter__" + test_seq_counter__=$(( $test_seq_counter__ + 1 )) + done } # This function can be used to schedule some commands to be run diff --git a/t/test-lib.sh b/t/test-lib.sh index 286c5f33d1..0055ebba46 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -322,6 +322,19 @@ else exec 4>/dev/null 3>/dev/null fi +# Send any "-x" output directly to stderr to avoid polluting tests +# which capture stderr. We can do this unconditionally since it +# has no effect if tracing isn't turned on. +# +# Note that this sets up the trace fd as soon as we assign the variable, so it +# must come after the creation of descriptor 4 above. Likewise, we must never +# unset this, as it has the side effect of closing descriptor 4, which we +# use to show verbose tests to the user. +# +# Note also that we don't need or want to export it. The tracing is local to +# this shell, and we would not want to influence any shells we exec. +BASH_XTRACEFD=4 + test_failure=0 test_count=0 test_fixed=0 diff --git a/transport-helper.c b/transport-helper.c index b934183236..bd666b29ec 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1152,7 +1152,7 @@ static void udt_close_if_finished(struct unidirectional_transfer *t) } /* - * Tries to read read data from source into buffer. If buffer is full, + * Tries to read data from source into buffer. If buffer is full, * no data is read. Returns 0 on success, -1 on error. */ static int udt_do_read(struct unidirectional_transfer *t) @@ -1166,7 +1166,7 @@ static int udt_do_read(struct unidirectional_transfer *t) bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse); if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) { - error("read(%s) failed: %s", t->src_name, strerror(errno)); + error_errno("read(%s) failed", t->src_name); return -1; } else if (bytes == 0) { transfer_debug("%s EOF (with %i bytes in buffer)", @@ -1193,7 +1193,7 @@ static int udt_do_write(struct unidirectional_transfer *t) transfer_debug("%s is writable", t->dest_name); bytes = xwrite(t->dest, t->buf, t->bufuse); if (bytes < 0 && errno != EWOULDBLOCK) { - error("write(%s) failed: %s", t->dest_name, strerror(errno)); + error_errno("write(%s) failed", t->dest_name); return -1; } else if (bytes > 0) { t->bufuse -= bytes; @@ -1306,7 +1306,7 @@ static int tloop_join(pid_t pid, const char *name) { int tret; if (waitpid(pid, &tret, 0) < 0) { - error("%s process failed to wait: %s", name, strerror(errno)); + error_errno("%s process failed to wait", name); return 1; } if (!WIFEXITED(tret) || WEXITSTATUS(tret)) { diff --git a/unpack-trees.c b/unpack-trees.c index 11308e9e5b..6bc9512a45 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -58,40 +58,74 @@ void setup_unpack_trees_porcelain(struct unpack_trees_options *opts, int i; const char **msgs = opts->msgs; const char *msg; - const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches"; - if (advice_commit_before_merge) - msg = "Your local changes to the following files would be overwritten by %s:\n%%s" - "Please, commit your changes or stash them before you can %s."; + if (!strcmp(cmd, "checkout")) + msg = advice_commit_before_merge + ? _("Your local changes to the following files would be overwritten by checkout:\n%%s" + "Please commit your changes or stash them before you can switch branches.") + : _("Your local changes to the following files would be overwritten by checkout:\n%%s"); + else if (!strcmp(cmd, "merge")) + msg = advice_commit_before_merge + ? _("Your local changes to the following files would be overwritten by merge:\n%%s" + "Please commit your changes or stash them before you can merge.") + : _("Your local changes to the following files would be overwritten by merge:\n%%s"); else - msg = "Your local changes to the following files would be overwritten by %s:\n%%s"; + msg = advice_commit_before_merge + ? _("Your local changes to the following files would be overwritten by %s:\n%%s" + "Please commit your changes or stash them before you can %s.") + : _("Your local changes to the following files would be overwritten by %s:\n%%s"); msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] = - xstrfmt(msg, cmd, cmd2); + xstrfmt(msg, cmd, cmd); msgs[ERROR_NOT_UPTODATE_DIR] = - "Updating the following directories would lose untracked files in it:\n%s"; - - if (advice_commit_before_merge) - msg = "The following untracked working tree files would be %s by %s:\n%%s" - "Please move or remove them before you can %s."; + _("Updating the following directories would lose untracked files in it:\n%s"); + + if (!strcmp(cmd, "checkout")) + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be removed by checkout:\n%%s" + "Please move or remove them before you can switch branches.") + : _("The following untracked working tree files would be removed by checkout:\n%%s"); + else if (!strcmp(cmd, "merge")) + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be removed by merge:\n%%s" + "Please move or remove them before you can merge.") + : _("The following untracked working tree files would be removed by merge:\n%%s"); else - msg = "The following untracked working tree files would be %s by %s:\n%%s"; - - msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, "removed", cmd, cmd2); - msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, "overwritten", cmd, cmd2); + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be removed by %s:\n%%s" + "Please move or remove them before you can %s.") + : _("The following untracked working tree files would be removed by %s:\n%%s"); + msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd); + + if (!strcmp(cmd, "checkout")) + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be overwritten by checkout:\n%%s" + "Please move or remove them before you can switch branches.") + : _("The following untracked working tree files would be overwritten by checkout:\n%%s"); + else if (!strcmp(cmd, "merge")) + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be overwritten by merge:\n%%s" + "Please move or remove them before you can merge.") + : _("The following untracked working tree files would be overwritten by merge:\n%%s"); + else + msg = advice_commit_before_merge + ? _("The following untracked working tree files would be overwritten by %s:\n%%s" + "Please move or remove them before you can %s.") + : _("The following untracked working tree files would be overwritten by %s:\n%%s"); + msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd); /* * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we * cannot easily display it as a list. */ - msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind."; + msgs[ERROR_BIND_OVERLAP] = _("Entry '%s' overlaps with '%s'. Cannot bind."); msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] = - "Cannot update sparse checkout: the following entries are not up-to-date:\n%s"; + _("Cannot update sparse checkout: the following entries are not up-to-date:\n%s"); msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] = - "The following Working tree files would be overwritten by sparse checkout update:\n%s"; + _("The following Working tree files would be overwritten by sparse checkout update:\n%s"); msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] = - "The following Working tree files would be removed by sparse checkout update:\n%s"; + _("The following Working tree files would be removed by sparse checkout update:\n%s"); opts->show_all_errors = 1; /* rejected paths may not have a static buffer */ @@ -168,7 +202,7 @@ static void display_error_msgs(struct unpack_trees_options *o) string_list_clear(rejects, 0); } if (something_displayed) - fprintf(stderr, "Aborting\n"); + fprintf(stderr, _("Aborting\n")); } /* @@ -1499,8 +1533,7 @@ static int verify_absent_1(const struct cache_entry *ce, path = xmemdupz(ce->name, len); if (lstat(path, &st)) - ret = error("cannot stat '%s': %s", path, - strerror(errno)); + ret = error_errno("cannot stat '%s'", path); else ret = check_ok_to_remove(path, len, DT_UNKNOWN, NULL, &st, error_type, o); @@ -1508,8 +1541,7 @@ static int verify_absent_1(const struct cache_entry *ce, return ret; } else if (lstat(ce->name, &st)) { if (errno != ENOENT) - return error("cannot stat '%s': %s", ce->name, - strerror(errno)); + return error_errno("cannot stat '%s'", ce->name); return 0; } else { return check_ok_to_remove(ce->name, ce_namelen(ce), diff --git a/upload-pack.c b/upload-pack.c index dc802a07c2..f19444df7b 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -174,8 +174,7 @@ static void create_pack_file(void) if (ret < 0) { if (errno != EINTR) { - error("poll failed, resuming: %s", - strerror(errno)); + error_errno("poll failed, resuming"); sleep(1); } continue; @@ -109,19 +109,11 @@ void NORETURN die(const char *err, ...) va_end(params); } -void NORETURN die_errno(const char *fmt, ...) +static const char *fmt_with_err(char *buf, int n, const char *fmt) { - va_list params; - char fmt_with_err[1024]; char str_error[256], *err; int i, j; - if (die_is_recursing()) { - fputs("fatal: recursion detected in die_errno handler\n", - stderr); - exit(128); - } - err = strerror(errno); for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) { if ((str_error[j++] = err[i++]) != '%') @@ -136,13 +128,37 @@ void NORETURN die_errno(const char *fmt, ...) } } str_error[j] = 0; - snprintf(fmt_with_err, sizeof(fmt_with_err), "%s: %s", fmt, str_error); + snprintf(buf, n, "%s: %s", fmt, str_error); + return buf; +} + +void NORETURN die_errno(const char *fmt, ...) +{ + char buf[1024]; + va_list params; + + if (die_is_recursing()) { + fputs("fatal: recursion detected in die_errno handler\n", + stderr); + exit(128); + } va_start(params, fmt); - die_routine(fmt_with_err, params); + die_routine(fmt_with_err(buf, sizeof(buf), fmt), params); va_end(params); } +int error_errno(const char *fmt, ...) +{ + char buf[1024]; + va_list params; + + va_start(params, fmt); + error_routine(fmt_with_err(buf, sizeof(buf), fmt), params); + va_end(params); + return -1; +} + #undef error int error(const char *err, ...) { @@ -154,6 +170,16 @@ int error(const char *err, ...) return -1; } +void warning_errno(const char *warn, ...) +{ + char buf[1024]; + va_list params; + + va_start(params, warn); + warn_routine(fmt_with_err(buf, sizeof(buf), warn), params); + va_end(params); +} + void warning(const char *warn, ...) { va_list params; @@ -48,7 +48,7 @@ static inline char *reencode_string(const char *in, int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding); /* - * Returns true if the the path would match ".git" after HFS case-folding. + * Returns true if the path would match ".git" after HFS case-folding. * The path should be NUL-terminated, but we will match variants of both ".git\0" * and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck * and verify_path(). diff --git a/vcs-svn/line_buffer.c b/vcs-svn/line_buffer.c index 57cc1cec03..e416caf8a4 100644 --- a/vcs-svn/line_buffer.c +++ b/vcs-svn/line_buffer.c @@ -53,9 +53,9 @@ long buffer_tmpfile_prepare_to_read(struct line_buffer *buf) { long pos = ftell(buf->infile); if (pos < 0) - return error("ftell error: %s", strerror(errno)); + return error_errno("ftell error"); if (fseek(buf->infile, 0, SEEK_SET)) - return error("seek error: %s", strerror(errno)); + return error_errno("seek error"); return pos; } diff --git a/vcs-svn/sliding_window.c b/vcs-svn/sliding_window.c index f11d490995..06d273c9e8 100644 --- a/vcs-svn/sliding_window.c +++ b/vcs-svn/sliding_window.c @@ -12,7 +12,7 @@ static int input_error(struct line_buffer *file) { if (!buffer_ferror(file)) return error("delta preimage ends early"); - return error("cannot read delta preimage: %s", strerror(errno)); + return error_errno("cannot read delta preimage"); } static int skip_or_whine(struct line_buffer *file, off_t gap) diff --git a/vcs-svn/svndiff.c b/vcs-svn/svndiff.c index 74c97c4543..75c753162a 100644 --- a/vcs-svn/svndiff.c +++ b/vcs-svn/svndiff.c @@ -64,13 +64,13 @@ static int write_strbuf(struct strbuf *sb, FILE *out) { if (fwrite(sb->buf, 1, sb->len, out) == sb->len) /* Success. */ return 0; - return error("cannot write delta postimage: %s", strerror(errno)); + return error_errno("cannot write delta postimage"); } static int error_short_read(struct line_buffer *input) { if (buffer_ferror(input)) - return error("error reading delta: %s", strerror(errno)); + return error_errno("error reading delta"); return error("invalid delta: unexpected end of file"); } diff --git a/vcs-svn/svndump.c b/vcs-svn/svndump.c index 31d1d83d45..e4b395963b 100644 --- a/vcs-svn/svndump.c +++ b/vcs-svn/svndump.c @@ -501,7 +501,7 @@ static void init(int report_fd) int svndump_init(const char *filename) { if (buffer_init(&input, filename)) - return error("cannot open %s: %s", filename ? filename : "NULL", strerror(errno)); + return error_errno("cannot open %s", filename ? filename : "NULL"); init(REPORT_FILENO); return 0; } @@ -509,7 +509,7 @@ int svndump_init(const char *filename) int svndump_init_fd(int in_fd, int back_fd) { if(buffer_fdinit(&input, xdup(in_fd))) - return error("cannot open fd %d: %s", in_fd, strerror(errno)); + return error_errno("cannot open fd %d", in_fd); init(xdup(back_fd)); return 0; } diff --git a/wildmatch.c b/wildmatch.c index f91ba99f32..57c8765805 100644 --- a/wildmatch.c +++ b/wildmatch.c @@ -136,7 +136,7 @@ static int dowild(const uchar *p, const uchar *text, unsigned int flags) /* * Try to advance faster when an asterisk is * followed by a literal. We know in this case - * that the the string before the literal + * that the string before the literal * must belong to "*". * If match_slash is false, do not look past * the first slash as it cannot belong to '*'. diff --git a/worktree.c b/worktree.c index 6181a66f1e..199b1ef94b 100644 --- a/worktree.c +++ b/worktree.c @@ -2,6 +2,8 @@ #include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "dir.h" +#include "wt-status.h" void free_worktrees(struct worktree **worktrees) { @@ -9,7 +11,7 @@ void free_worktrees(struct worktree **worktrees) for (i = 0; worktrees[i]; i++) { free(worktrees[i]->path); - free(worktrees[i]->git_dir); + free(worktrees[i]->id); free(worktrees[i]->head_ref); free(worktrees[i]); } @@ -18,7 +20,7 @@ void free_worktrees(struct worktree **worktrees) /* * read 'path_to_ref' into 'ref'. Also if is_detached is not NULL, - * set is_detached to 1 (0) if the ref is detatched (is not detached). + * set is_detached to 1 (0) if the ref is detached (is not detached). * * $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so * for linked worktrees, `resolve_ref_unsafe()` won't work (it uses @@ -74,13 +76,11 @@ static struct worktree *get_main_worktree(void) struct worktree *worktree = NULL; struct strbuf path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT; int is_bare = 0; int is_detached = 0; - strbuf_addf(&gitdir, "%s", absolute_path(get_git_common_dir())); - strbuf_addbuf(&worktree_path, &gitdir); + strbuf_addstr(&worktree_path, absolute_path(get_git_common_dir())); is_bare = !strbuf_strip_suffix(&worktree_path, "/.git"); if (is_bare) strbuf_strip_suffix(&worktree_path, "/."); @@ -92,15 +92,15 @@ static struct worktree *get_main_worktree(void) worktree = xmalloc(sizeof(struct worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); - worktree->git_dir = strbuf_detach(&gitdir, NULL); + worktree->id = NULL; worktree->is_bare = is_bare; worktree->head_ref = NULL; worktree->is_detached = is_detached; + worktree->is_current = 0; add_head_info(&head_ref, worktree); done: strbuf_release(&path); - strbuf_release(&gitdir); strbuf_release(&worktree_path); strbuf_release(&head_ref); return worktree; @@ -111,16 +111,13 @@ static struct worktree *get_linked_worktree(const char *id) struct worktree *worktree = NULL; struct strbuf path = STRBUF_INIT; struct strbuf worktree_path = STRBUF_INIT; - struct strbuf gitdir = STRBUF_INIT; struct strbuf head_ref = STRBUF_INIT; int is_detached = 0; if (!id) die("Missing linked worktree name"); - strbuf_addf(&gitdir, "%s/worktrees/%s", - absolute_path(get_git_common_dir()), id); - strbuf_addf(&path, "%s/gitdir", gitdir.buf); + strbuf_git_common_path(&path, "worktrees/%s/gitdir", id); if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0) /* invalid gitdir file */ goto done; @@ -140,20 +137,39 @@ static struct worktree *get_linked_worktree(const char *id) worktree = xmalloc(sizeof(struct worktree)); worktree->path = strbuf_detach(&worktree_path, NULL); - worktree->git_dir = strbuf_detach(&gitdir, NULL); + worktree->id = xstrdup(id); worktree->is_bare = 0; worktree->head_ref = NULL; worktree->is_detached = is_detached; + worktree->is_current = 0; add_head_info(&head_ref, worktree); done: strbuf_release(&path); - strbuf_release(&gitdir); strbuf_release(&worktree_path); strbuf_release(&head_ref); return worktree; } +static void mark_current_worktree(struct worktree **worktrees) +{ + struct strbuf git_dir = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + int i; + + strbuf_addstr(&git_dir, absolute_path(get_git_dir())); + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; + strbuf_addstr(&path, absolute_path(get_worktree_git_dir(wt))); + wt->is_current = !fspathcmp(git_dir.buf, path.buf); + strbuf_reset(&path); + if (wt->is_current) + break; + } + strbuf_release(&git_dir); + strbuf_release(&path); +} + struct worktree **get_worktrees(void) { struct worktree **list = NULL; @@ -185,35 +201,105 @@ struct worktree **get_worktrees(void) } ALLOC_GROW(list, counter + 1, alloc); list[counter] = NULL; + + mark_current_worktree(list); return list; } -char *find_shared_symref(const char *symref, const char *target) +const char *get_worktree_git_dir(const struct worktree *wt) +{ + if (!wt) + return get_git_dir(); + else if (!wt->id) + return get_git_common_dir(); + else + return git_common_path("worktrees/%s", wt->id); +} + +int is_worktree_being_rebased(const struct worktree *wt, + const char *target) +{ + struct wt_status_state state; + int found_rebase; + + memset(&state, 0, sizeof(state)); + found_rebase = wt_status_check_rebase(wt, &state) && + ((state.rebase_in_progress || + state.rebase_interactive_in_progress) && + state.branch && + starts_with(target, "refs/heads/") && + !strcmp(state.branch, target + strlen("refs/heads/"))); + free(state.branch); + free(state.onto); + return found_rebase; +} + +int is_worktree_being_bisected(const struct worktree *wt, + const char *target) { - char *existing = NULL; + struct wt_status_state state; + int found_rebase; + + memset(&state, 0, sizeof(state)); + found_rebase = wt_status_check_bisect(wt, &state) && + state.branch && + starts_with(target, "refs/heads/") && + !strcmp(state.branch, target + strlen("refs/heads/")); + free(state.branch); + return found_rebase; +} + +/* + * note: this function should be able to detect shared symref even if + * HEAD is temporarily detached (e.g. in the middle of rebase or + * bisect). New commands that do similar things should update this + * function as well. + */ +const struct worktree *find_shared_symref(const char *symref, + const char *target) +{ + const struct worktree *existing = NULL; struct strbuf path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; - struct worktree **worktrees = get_worktrees(); + static struct worktree **worktrees; int i = 0; + if (worktrees) + free_worktrees(worktrees); + worktrees = get_worktrees(); + for (i = 0; worktrees[i]; i++) { + struct worktree *wt = worktrees[i]; + + if (wt->is_detached && !strcmp(symref, "HEAD")) { + if (is_worktree_being_rebased(wt, target)) { + existing = wt; + break; + } + if (is_worktree_being_bisected(wt, target)) { + existing = wt; + break; + } + } + strbuf_reset(&path); strbuf_reset(&sb); - strbuf_addf(&path, "%s/%s", worktrees[i]->git_dir, symref); + strbuf_addf(&path, "%s/%s", + get_worktree_git_dir(wt), + symref); if (parse_ref(path.buf, &sb, NULL)) { continue; } if (!strcmp(sb.buf, target)) { - existing = xstrdup(worktrees[i]->path); + existing = wt; break; } } strbuf_release(&path); strbuf_release(&sb); - free_worktrees(worktrees); return existing; } diff --git a/worktree.h b/worktree.h index b4b3dda792..13949093cc 100644 --- a/worktree.h +++ b/worktree.h @@ -3,11 +3,12 @@ struct worktree { char *path; - char *git_dir; + char *id; char *head_ref; unsigned char head_sha1[20]; int is_detached; int is_bare; + int is_current; }; /* Functions for acting on the information about worktrees. */ @@ -23,16 +24,33 @@ struct worktree { extern struct worktree **get_worktrees(void); /* + * Return git dir of the worktree. Note that the path may be relative. + * If wt is NULL, git dir of current worktree is returned. + */ +extern const char *get_worktree_git_dir(const struct worktree *wt); + +/* * Free up the memory for worktree(s) */ extern void free_worktrees(struct worktree **); /* * Check if a per-worktree symref points to a ref in the main worktree - * or any linked worktree, and return the path to the exising worktree - * if it is. Returns NULL if there is no existing ref. The caller is - * responsible for freeing the returned path. + * or any linked worktree, and return the worktree that holds the ref, + * or NULL otherwise. The result may be destroyed by the next call. + */ +extern const struct worktree *find_shared_symref(const char *symref, + const char *target); + +int is_worktree_being_rebased(const struct worktree *wt, const char *target); +int is_worktree_being_bisected(const struct worktree *wt, const char *target); + +/* + * Similar to git_path() but can produce paths for a specified + * worktree instead of current one */ -extern char *find_shared_symref(const char *symref, const char *target); +extern const char *worktree_git_path(const struct worktree *wt, + const char *fmt, ...) + __attribute__((format (printf, 2, 3))); #endif diff --git a/wrap-for-bin.sh b/wrap-for-bin.sh index db0ec6a737..22b6e4948f 100644 --- a/wrap-for-bin.sh +++ b/wrap-for-bin.sh @@ -17,6 +17,7 @@ fi GITPERLLIB='@@BUILD_DIR@@/perl/blib/lib'"${GITPERLLIB:+:$GITPERLLIB}" GIT_TEXTDOMAINDIR='@@BUILD_DIR@@/po/build/locale' PATH='@@BUILD_DIR@@/bin-wrappers:'"$PATH" + export GIT_EXEC_PATH GITPERLLIB PATH GIT_TEXTDOMAINDIR if test -n "$GIT_TEST_GDB" @@ -555,7 +555,7 @@ static int warn_if_unremovable(const char *op, const char *file, int rc) if (!rc || errno == ENOENT) return 0; err = errno; - warning("unable to %s %s: %s", op, file, strerror(errno)); + warning_errno("unable to %s %s", op, file); errno = err; return rc; } @@ -591,7 +591,7 @@ int remove_or_warn(unsigned int mode, const char *file) void warn_on_inaccessible(const char *path) { - warning(_("unable to access '%s': %s"), path, strerror(errno)); + warning_errno(_("unable to access '%s'"), path); } static int access_error_is_ok(int err, unsigned flag) diff --git a/wt-status.c b/wt-status.c index 1ea2ebe4c0..4f27bd62af 100644 --- a/wt-status.c +++ b/wt-status.c @@ -15,6 +15,7 @@ #include "column.h" #include "strbuf.h" #include "utf8.h" +#include "worktree.h" static const char cut_line[] = "------------------------ >8 ------------------------\n"; @@ -950,6 +951,7 @@ static void show_merge_in_progress(struct wt_status *s, status_printf_ln(s, color, _(" (fix conflicts and run \"git commit\")")); } else { + s-> commitable = 1; status_printf_ln(s, color, _("All conflicts fixed but you are still merging.")); if (s->hints) @@ -1262,13 +1264,13 @@ static void show_bisect_in_progress(struct wt_status *s, /* * Extract branch information from rebase/bisect */ -static char *read_and_strip_branch(const char *path) +static char *get_branch(const struct worktree *wt, const char *path) { struct strbuf sb = STRBUF_INIT; unsigned char sha1[20]; const char *branch_name; - if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0) + if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0) goto got_nothing; while (sb.len && sb.buf[sb.len - 1] == '\n') @@ -1360,40 +1362,62 @@ static void wt_status_get_detached_from(struct wt_status_state *state) strbuf_release(&cb.buf); } -void wt_status_get_state(struct wt_status_state *state, - int get_detached_from) +int wt_status_check_rebase(const struct worktree *wt, + struct wt_status_state *state) { struct stat st; - unsigned char sha1[20]; - if (!stat(git_path_merge_head(), &st)) { - state->merge_in_progress = 1; - } else if (!stat(git_path("rebase-apply"), &st)) { - if (!stat(git_path("rebase-apply/applying"), &st)) { + if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) { + if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) { state->am_in_progress = 1; - if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size) + if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size) state->am_empty_patch = 1; } else { state->rebase_in_progress = 1; - state->branch = read_and_strip_branch("rebase-apply/head-name"); - state->onto = read_and_strip_branch("rebase-apply/onto"); + state->branch = get_branch(wt, "rebase-apply/head-name"); + state->onto = get_branch(wt, "rebase-apply/onto"); } - } else if (!stat(git_path("rebase-merge"), &st)) { - if (!stat(git_path("rebase-merge/interactive"), &st)) + } else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) { + if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st)) state->rebase_interactive_in_progress = 1; else state->rebase_in_progress = 1; - state->branch = read_and_strip_branch("rebase-merge/head-name"); - state->onto = read_and_strip_branch("rebase-merge/onto"); + state->branch = get_branch(wt, "rebase-merge/head-name"); + state->onto = get_branch(wt, "rebase-merge/onto"); + } else + return 0; + return 1; +} + +int wt_status_check_bisect(const struct worktree *wt, + struct wt_status_state *state) +{ + struct stat st; + + if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) { + state->bisect_in_progress = 1; + state->branch = get_branch(wt, "BISECT_START"); + return 1; + } + return 0; +} + +void wt_status_get_state(struct wt_status_state *state, + int get_detached_from) +{ + struct stat st; + unsigned char sha1[20]; + + if (!stat(git_path_merge_head(), &st)) { + state->merge_in_progress = 1; + } else if (wt_status_check_rebase(NULL, state)) { + ; /* all set */ } else if (!stat(git_path_cherry_pick_head(), &st) && !get_sha1("CHERRY_PICK_HEAD", sha1)) { state->cherry_pick_in_progress = 1; hashcpy(state->cherry_pick_head_sha1, sha1); } - if (!stat(git_path("BISECT_LOG"), &st)) { - state->bisect_in_progress = 1; - state->branch = read_and_strip_branch("BISECT_START"); - } + wt_status_check_bisect(NULL, state); if (!stat(git_path_revert_head(), &st) && !get_sha1("REVERT_HEAD", sha1)) { state->revert_in_progress = 1; diff --git a/wt-status.h b/wt-status.h index c9b3b744e9..2ca93f6957 100644 --- a/wt-status.h +++ b/wt-status.h @@ -6,6 +6,8 @@ #include "color.h" #include "pathspec.h" +struct worktree; + enum color_wt_status { WT_STATUS_HEADER = 0, WT_STATUS_UPDATED, @@ -100,6 +102,10 @@ void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); void wt_status_get_state(struct wt_status_state *state, int get_detached_from); +int wt_status_check_rebase(const struct worktree *wt, + struct wt_status_state *state); +int wt_status_check_bisect(const struct worktree *wt, + struct wt_status_state *state); void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); |