From: Junio C Hamano Date: Fri, 6 May 2016 21:45:42 +0000 (-0700) Subject: Merge branch 'ld/p4-test-py3' X-Git-Tag: v2.9.0-rc0~70 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/ca158f4633e40fb8a6e7e6b78dc7ad559718a19b?hp=1fb3fb4e6d4709a3b8311deff4b109af67efb514 Merge branch 'ld/p4-test-py3' 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. * ld/p4-test-py3: git-p4 tests: time_in_seconds should use $PYTHON_PATH git-p4 tests: work with python3 as well as python2 git-p4 tests: cd to / before running python --- diff --git a/.gitignore b/.gitignore index 5087ce1eb7..05cb58a3d4 100644 --- a/.gitignore +++ b/.gitignore @@ -179,39 +179,6 @@ /gitweb/gitweb.cgi /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* -/test-chmtime -/test-ctype -/test-config -/test-date -/test-delta -/test-dump-cache-tree -/test-dump-split-index -/test-dump-untracked-cache -/test-fake-ssh -/test-scrap-cache-tree -/test-genrandom -/test-hashmap -/test-index-version -/test-line-buffer -/test-match-trees -/test-mergesort -/test-mktemp -/test-parse-options -/test-path-utils -/test-prio-queue -/test-read-cache -/test-regex -/test-revision-walking -/test-run-command -/test-sha1 -/test-sha1-array -/test-sigchain -/test-string-list -/test-submodule-config -/test-subprocess -/test-svn-fe -/test-urlmatch-normalization -/test-wildmatch /common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/RelNotes/2.8.2.txt b/Documentation/RelNotes/2.8.2.txt index 3db67f4c55..447b1933a8 100644 --- a/Documentation/RelNotes/2.8.2.txt +++ b/Documentation/RelNotes/2.8.2.txt @@ -52,4 +52,19 @@ Fixes since v2.8.1 nothing into an unborn history (which is arguably unusual usage, which perhaps was the reason why nobody noticed it). + * Build updates for MSVC. + + * "git diff -M" used to work better when two originally identical + files A and B got renamed to X/A and X/B by pairing A to X/A and B + to X/B, but this was broken in the 2.0 timeframe. + + * "git send-pack --all " was broken when its command line + option parsing was written in the 2.6 timeframe. + + * When running "git blame $path" with unnormalized data in the index + for the path, the data in the working tree was blamed, even though + "git add" would not have changed what is already in the index, due + to "safe crlf" that disables the line-end conversion. It has been + corrected. + Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt new file mode 100644 index 0000000000..6030f38063 --- /dev/null +++ b/Documentation/RelNotes/2.8.3.txt @@ -0,0 +1,61 @@ +Git v2.8.3 Release Notes +======================== + +Fixes since v2.8.2 +------------------ + + * "git send-email" now uses a more readable timestamps when + formulating a message ID. + + * The repository set-up sequence has been streamlined (the biggest + change is that there is no longer git_config_early()), so that we + do not attempt to look into refs/* when we know we do not have a + Git repository. + + * When "git worktree" feature is in use, "git branch -d" allowed + deletion of a branch that is checked out in another worktree + + * When "git worktree" feature is in use, "git branch -m" renamed a + branch that is checked out in another worktree without adjusting + the HEAD symbolic ref for the worktree. + + * "git format-patch --help" showed `-s` and `--no-patch` as if these + are valid options to the command. We already hide `--patch` option + from the documentation, because format-patch is about showing the + diff, and the documentation now hides these options as well. + + * A change back in version 2.7 to "git branch" broke display of a + symbolic ref in a non-standard place in the refs/ hierarchy (we + expect symbolic refs to appear in refs/remotes/*/HEAD to point at + the primary branch the remote has, and as .git/HEAD to point at the + branch we locally checked out). + + * A partial rewrite of "git submodule" in the 2.7 timeframe changed + the way the gitdir: pointer in the submodules point at the real + repository location to use absolute paths by accident. This has + been corrected. + + * "git commit" misbehaved in a few minor ways when an empty message + is given via -m '', all of which has been corrected. + + * Support for CRAM-MD5 authentication method in "git imap-send" did + not work well. + + * The socks5:// proxy support added back in 2.6.4 days was not aware + that socks5h:// proxies behave differently. + + * "git config" had a codepath that tried to pass a NULL to + printf("%s"), which nobody seems to have noticed. + + * On Cygwin, object creation uses the "create a temporary and then + rename it to the final name" pattern, not "create a temporary, + hardlink it to the final name and then unlink the temporary" + pattern. + + This is necessary to use Git on Windows shared directories, and is + already enabled for the MinGW and plain Windows builds. It also + has been used in Cygwin packaged versions of Git for quite a while. + See http://thread.gmane.org/gmane.comp.version-control.git/291853 + and http://thread.gmane.org/gmane.comp.version-control.git/275680. + +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 new file mode 100644 index 0000000000..0d983a5975 --- /dev/null +++ b/Documentation/RelNotes/2.9.0.txt @@ -0,0 +1,326 @@ +Git 2.9 Release Notes +===================== + +Backward compatibility note +--------------------------- + +The end-user facing Porcelain level commands in the "git diff" and +"git log" by default enables the rename detection; you can still use +"diff.renames" configuration variable to disable this. + +Merging two branches that have no common ancestor with "git merge" is +by default forbidden now to prevent creating such an unusual merge by +mistake. + +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. + + +Updates since v2.8 +------------------ + +UI, Workflows & Features + + * The end-user facing Porcelain level commands like "diff" and "log" + now enables the rename detection by default. + + * The credential.helper configuration variable is cumulative and + there is no good way to override it from the command line. As + a special case, giving an empty string as its value now serves + as the signal to clear the values specified in various files. + + * A new "interactive.diffFilter" configuration can be used to + customize the diff shown in "git add -i" session. + + * "git p4" now allows P4 author names to be mapped to Git author + names. + + * "git rebase -x" can be used without passing "-i" option. + + * "git -c credential.= submodule" can now be used to + propagate configuration variables related to credential helper + down to the submodules. + + * "git tag" can create an annotated tag without explicitly given an + "-a" (or "-s") option (i.e. when a tag message is given). A new + configuration variable, tag.forceSignAnnotated, can be used to tell + the command to create signed tag in such a situation. + + * "git merge" used to allow merging two branches that have no common + base by default, which led to a brand new history of an existing + project created and then get pulled by an unsuspecting maintainer, + which allowed an unnecessary parallel history merged into the + existing project. The command has been taught not to allow this by + default, with an escape hatch "--allow-unrelated-histories" option + to be used in a rare event that merges histories of two projects + that started their lives independently. + + * "git pull" has been taught to pass --allow-unrelated-histories + option to underlying "git merge". + + * "git apply -v" learned to report paths in the patch that were + skipped via --include/--exclude mechanism or being outside the + current working directory. + + * Shell completion (in contrib/) updates. + + * The commit object name reported when "rebase -i" stops has been + shortened. + + * "git worktree add" can be given "--no-checkout" option to only + create an empty worktree without checking out the files. + + * "git mergetools" learned to drive ExamDiff. + + * "git pull --rebase" learned "--[no-]autostash" option, so that + the rebase.autostash configuration variable set to true can be + overridden from the command line. + + * When "git log" shows the log message indented by 4-spaces, the + remainder of a line after a HT does not align in the way the author + originally intended. The command now expands tabs by default in + such a case, and allows the users to override it with a new option, + "--no-expand-tabs". + + * "git send-email" now uses a more readable timestamps when + formulating a message ID. + + * "git rerere" can encounter two or more files with the same conflict + signature that have to be resolved in different ways, but there was + no way to record these separate resolutions. + (merge 890fca8 jc/rerere-multi later to maint). + + * "git p4" learned to record P4 jobs in Git commit that imports from + the history in Perforce. + + * "git describe --contains" often made a hard-to-justify choice of + tag to give name to a given commit, because it tried to come up + with a name with smallest number of hops from a tag, causing an old + commit whose close descendant that is recently tagged were not + described with respect to an old tag but with a newer tag. It did + not help that its computation of "hop" count was further tweaked to + penalize being on a side branch of a merge. The logic has been + updated to favor using the tag with the oldest tagger date, which + is a lot easier to explain to the end users: "We describe a commit + in terms of the (chronologically) oldest tag that contains the + commit." + (merge 7550424 js/name-rev-use-oldest-ref later to maint). + + +Performance, Internal Implementation, Development Support etc. + + * The embedded args argv-array in the child process is used to build + the command line to run pack-objects instead of using a separate + array of strings. + + * A test for tags has been restructured so that more parts of it can + easily be run on a platform without a working GnuPG. + + * The startup_info data, which records if we are working inside a + repository (among other things), are now uniformly available to Git + subcommand implementations, and Git avoids attempting to touch + references when we are not in a repository. + + * The command line argument parser for "receive-pack" has been + rewritten to use parse-options. + + * A major part of "git submodule update" has been ported to C to take + advantage of the recently added framework to run download tasks in + parallel. + + * Rename bunch of tests on "git clone" for better organization. + + * The tests that involve running httpd leaked the system-wide + configuration in /etc/gitconfig to the tested environment. + + * Build updates for MSVC. + + * The repository set-up sequence has been streamlined (the biggest + change is that there is no longer git_config_early()), so that we + do not attempt to look into refs/* when we know we do not have a + Git repository. + + * Code restructuring around the "refs" area to prepare for pluggable + refs backends. + + * Sources to many test helper binaries (and the generated helpers) + 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). + + * "merge-recursive" strategy incorrectly checked if a path that is + involved in its internal merge exists in the working tree. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.8 +---------------- + +Unless otherwise noted, all the fixes since v2.8 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * "git config --get-urlmatch", unlike other variants of the "git + config --get" family, did not signal error with its exit status + when there was no matching configuration. + + * The "--local-env-vars" and "--resolve-git-dir" options of "git + rev-parse" failed to work outside a repository when the command's + option parsing was rewritten in 1.8.5 era. + + * "git index-pack --keep[=] pack-$name.pack" simply did not work. + + * Fetching of history by naming a commit object name directly didn't + work across remote-curl transport. + + * A small memory leak in an error codepath has been plugged in xdiff + code. + + * strbuf_getwholeline() did not NUL-terminate the buffer on certain + corner cases in its error codepath. + + * "git mergetool" did not work well with conflicts that both sides + deleted. + + * "git send-email" had trouble parsing alias file in mailrc format + when lines in it had trailing whitespaces on them. + + * When "git merge --squash" stopped due to conflict, the concluding + "git commit" failed to read in the SQUASH_MSG that shows the log + messages from all the squashed commits. + + * "git merge FETCH_HEAD" dereferenced NULL pointer when merging + nothing into an unborn history (which is arguably unusual usage, + which perhaps was the reason why nobody noticed it). + + * When "git worktree" feature is in use, "git branch -d" allowed + deletion of a branch that is checked out in another worktree, + which was wrong. + + * When "git worktree" feature is in use, "git branch -m" renamed a + branch that is checked out in another worktree without adjusting + the HEAD symbolic ref for the worktree. + + * "git diff -M" used to work better when two originally identical + files A and B got renamed to X/A and X/B by pairing A to X/A and B + to X/B, but this was broken in the 2.0 timeframe. + + * "git send-pack --all " was broken when its command line + option parsing was written in the 2.6 timeframe. + + * "git format-patch --help" showed `-s` and `--no-patch` as if these + are valid options to the command. We already hide `--patch` option + from the documentation, because format-patch is about showing the + diff, and the documentation now hides these options as well. + + * When running "git blame $path" with unnormalized data in the index + for the path, the data in the working tree was blamed, even though + "git add" would not have changed what is already in the index, due + to "safe crlf" that disables the line-end conversion. It has been + corrected. + + * A change back in version 2.7 to "git branch" broke display of a + symbolic ref in a non-standard place in the refs/ hierarchy (we + expect symbolic refs to appear in refs/remotes/*/HEAD to point at + the primary branch the remote has, and as .git/HEAD to point at the + branch we locally checked out). + + * A partial rewrite of "git submodule" in the 2.7 timeframe changed + the way the gitdir: pointer in the submodules point at the real + repository location to use absolute paths by accident. This has + been corrected. + + * "git commit" misbehaved in a few minor ways when an empty message + is given via -m '', all of which has been corrected. + + * Support for CRAM-MD5 authentication method in "git imap-send" did + not work well. + + * Upcoming OpenSSL 1.1.0 will break compilation b updating a few APIs + we use in imap-send, which has been adjusted for the change. + (merge 1245c74 ky/imap-send-openssl-1.1.0 later to maint). + + * The socks5:// proxy support added back in 2.6.4 days was not aware + that socks5h:// proxies behave differently. + + * "git config" had a codepath that tried to pass a NULL to + printf("%s"), which nobody seems to have noticed. + + * On Cygwin, object creation uses the "create a temporary and then + rename it to the final name" pattern, not "create a temporary, + hardlink it to the final name and then unlink the temporary" + pattern. + + This is necessary to use Git on Windows shared directories, and is + already enabled for the MinGW and plain Windows builds. It also + has been used in Cygwin packaged versions of Git for quite a while. + See http://thread.gmane.org/gmane.comp.version-control.git/291853 + + * "merge-octopus" strategy did not ensure that the index is clean + when merge begins. + + * When "git merge" notices that the merge can be resolved purely at + the tree level (without having to merge blobs) and the resulting + tree happens to already exist in the object store, it forgot to + update the index, which lead to an inconsistent state for later + operations. + + * "git submodule" reports the paths of submodules the command + recurses into, but this was incorrect when the command was not run + from the root level of the superproject. + (merge 2ab5660 sb/submodule-path-misc-bugs later to maint). + + * 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. + (merge d3c06c1 da/user-useconfigonly later to maint). + + * "git mv old new" did not adjust the path for a submodule that lives + as a subdirectory inside old/ directory correctly. + (merge a127331 sb/mv-submodule-fix later to maint). + + * "git replace -e" did not honour "core.editor" configuration. + (merge 36b1437 js/replace-edit-use-editor-configuration later to maint). + + * "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. + (merge c4b2751 jk/push-client-deadlock-fix later to maint). + + * mmap emulation on Windows has been optimized and work better without + consuming paging store when not needed. + (merge d5425d1 js/win32-mmap later to maint). + + * A question by "git send-email" to ask the identity of the sender + has been updated. + (merge 0d6b21e jd/send-email-to-whom later to maint). + + * UI consistency improvements for "git mergetool". + (merge cce076e nf/mergetool-prompt later to maint). + + * Other minor clean-ups and documentation updates + (merge 8b5a3e9 kn/for-each-tag-branch later to maint). + (merge 9c60d9f sb/misc-cleanups later to maint). + (merge 7a6a44c cc/apply later to maint). + (merge 6594883 nd/remove-unused later to maint). + (merge 0ff7410 sg/test-lib-simplify-expr-away later to maint). + (merge 060e776 jk/fix-attribute-macro-in-2.5 later to maint). + (merge d16df0c rt/string-list-lookup-cleanup later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 2cd6bdd7d2..42d2b50477 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1113,8 +1113,9 @@ commit.template:: credential.helper:: Specify an external helper to be called when a username or password credential is needed; the helper may consult external - storage to avoid prompting the user for the credentials. See - linkgit:gitcredentials[7] for details. + storage to avoid prompting the user for the credentials. Note + that multiple helpers may be defined. See linkgit:gitcredentials[7] + for details. credential.useHttpPath:: When acquiring credentials, consider the "path" component of an http @@ -1886,6 +1887,14 @@ interactive.singleKey:: setting is silently ignored if portable keystroke input is not available; requires the Perl module Term::ReadKey. +interactive.diffFilter:: + When an interactive command (such as `git add --patch`) shows + a colorized diff, git will pipe the diff through the shell + command defined by this configuration variable. The command may + mark up the diff further for human consumption, provided that it + retains a one-to-one correspondence with the lines in the + original diff. Defaults to disabled (no filtering). + log.abbrevCommit:: If true, makes linkgit:git-log[1], linkgit:git-show[1], and linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may @@ -2729,6 +2738,17 @@ submodule..ignore:: "--ignore-submodules" option. The 'git submodule' commands are not affected by this setting. +submodule.fetchJobs:: + Specifies how many submodules are fetched/cloned at the same time. + A positive integer allows up to that number of submodules fetched + in parallel. A value of 0 will give some reasonable default. + If unset, it defaults to 1. + +tag.forceSignAnnotated:: + A boolean to specify whether annotated tags created should be GPG signed. + If `--annotate` is specified on the command line, it takes + precedence over this option. + tag.sort:: This variable controls the sort ordering of tags when displayed by linkgit:git-tag[1]. Without the "--sort=" option provided, the diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 6eaa45271c..edba56522b 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -108,9 +108,13 @@ diff.renameLimit:: detection; equivalent to the 'git diff' option '-l'. diff.renames:: - Tells Git to detect renames. If set to any boolean value, it - will enable basic rename detection. If set to "copies" or - "copy", it will detect copies, as well. + Whether and how Git detects renames. If set to "false", + rename detection is disabled. If set to "true", basic rename + detection is enabled. If set to "copies" or "copy", Git will + detect copies, as well. Defaults to true. Note that this + affects only 'git diff' Porcelain like linkgit:git-diff[1] and + linkgit:git-log[1], and not lower level commands such as + linkgit:git-diff-files[1]. diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 32f48ed647..4b0318e2ac 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -26,12 +26,12 @@ ifndef::git-format-patch[] ifdef::git-diff[] This is the default. endif::git-diff[] -endif::git-format-patch[] -s:: --no-patch:: Suppress diff output. Useful for commands like `git show` that show the patch by default, or to cancel the effect of `--patch`. +endif::git-format-patch[] -U:: --unified=:: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index b7c467a001..45d74be297 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -14,7 +14,7 @@ SYNOPSIS [-o ] [-b ] [-u ] [--reference ] [--dissociate] [--separate-git-dir ] [--depth ] [--[no-]single-branch] - [--recursive | --recurse-submodules] [--] + [--recursive | --recurse-submodules] [--jobs ] [--] [] DESCRIPTION @@ -219,6 +219,10 @@ objects from the source repository into a pack in the cloned repository. The result is Git repository can be separated from working tree. +-j :: +--jobs :: + The number of submodules fetched at the same time. + Defaults to the `submodule.fetchJobs` option. :: The (possibly remote) repository to clone from. See the diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 6fc08e3d89..6843114fc0 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -58,10 +58,10 @@ that location (you can say '--local' but that is the default). This command will fail with non-zero status upon error. Some exit codes are: -- The config file is invalid (ret=3), -- can not write to the config file (ret=4), +- The section or key is invalid (ret=1), - no section or name was provided (ret=2), -- the section or key is invalid (ret=1), +- the config file is invalid (ret=3), +- the config file cannot be written (ret=4), - you try to unset an option which does not exist (ret=5), - you try to unset/set an option for which multiple lines match (ret=5), or - you try to use an invalid regexp (ret=6). diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 012e8f9a08..c52578bb87 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -76,7 +76,7 @@ OPTIONS specified commit (HEAD if not specified). --contains []:: - Only list tags which contain the specified commit (HEAD if not + Only list refs which contain the specified commit (HEAD if not specified). FIELD NAMES diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 07f7295ec8..b758d5556c 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [--[no-]edit] [-s ] [-X ] [-S[]] + [--[no-]allow-unrelated-histories] [--[no-]rerere-autoupdate] [-m ] [...] 'git merge' HEAD ... 'git merge' --abort diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 35e3170918..88ba42b455 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -551,6 +551,17 @@ git-p4.keepEmptyCommits:: A changelist that contains only excluded files will be imported as an empty commit if this boolean option is set to true. +git-p4.mapUser:: + Map a P4 user to a name and email address in Git. Use a string + with the following format to create a mapping: ++ +------------- +git config --add git-p4.mapUser "p4user = First Last " +------------- ++ +A mapping will override any user information from P4. Mappings for +multiple P4 user can be defined. + Submit variables ~~~~~~~~~~~~~~~~ git-p4.detectRenames:: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index a62a2a615d..d033b258e5 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -128,6 +128,15 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. +--autostash:: +--no-autostash:: + Before starting rebase, stash local modifications away (see + linkgit:git-stash[1]) if needed, and apply the stash when + done. `--no-autostash` is useful to override the `rebase.autoStash` + configuration variable (see linkgit:git-config[1]). ++ +This option is only valid when "--rebase" is used. + Options related to fetching ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6ed610a031..0387b40e0a 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -391,9 +391,6 @@ idea unless you know what you are doing (see BUGS below). final history. will be interpreted as one or more shell commands. + -This option can only be used with the `--interactive` option -(see INTERACTIVE MODE below). -+ You may execute several commands by either using one instance of `--exec` with several commands: + @@ -406,6 +403,9 @@ or by giving more than one `--exec`: If `--autosquash` is used, "exec" lines will not be appended for the intermediate commits, and will only appear at the end of each squash/fixup series. ++ +This uses the `--interactive` machinery internally, but it can be run +without an explicit `--interactive`. --root:: Rebase all commits reachable from , instead of diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 1572f058f5..13adebf7b7 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -16,7 +16,7 @@ SYNOPSIS 'git submodule' [--quiet] deinit [-f|--force] [--] ... 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase|--merge] [--reference ] - [--depth ] [--recursive] [--] [...] + [--depth ] [--recursive] [--jobs ] [--] [...] 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) ] [commit] [--] [...] 'git submodule' [--quiet] foreach [--recursive] @@ -377,6 +377,11 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully. clone with a history truncated to the specified number of revisions. See linkgit:git-clone[1] +-j :: +--jobs :: + This option is only valid for the update command. + Clone new submodules in parallel with as many jobs. + Defaults to the `submodule.fetchJobs` option. ...:: Paths to submodule(s). When specified this will restrict the command diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 62c76c1c89..c62234538b 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees SYNOPSIS -------- [verse] -'git worktree add' [-f] [--detach] [-b ] [] +'git worktree add' [-f] [--detach] [--checkout] [-b ] [] 'git worktree prune' [-n] [-v] [--expire ] 'git worktree list' [--porcelain] @@ -87,6 +87,12 @@ OPTIONS With `add`, detach HEAD in the new working tree. See "DETACHED HEAD" in linkgit:git-checkout[1]. +--[no-]checkout:: + By default, `add` checks out ``, however, `--no-checkout` can + be used to suppress checkout in order to make customizations, + such as configuring sparse-checkout. See "Sparse checkout" + in linkgit:git-read-tree[1]. + -n:: --dry-run:: With `prune`, do not remove anything; just report what it would diff --git a/Documentation/git.txt b/Documentation/git.txt index 8afe349781..34ff007a98 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ 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.1/git.html[documentation for release 2.8.1] +* link:v2.8.2/git.html[documentation for release 2.8.2] * 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.0.txt[2.8]. diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt index 1c75be0803..f3a75d1ce1 100644 --- a/Documentation/gitcredentials.txt +++ b/Documentation/gitcredentials.txt @@ -106,6 +106,11 @@ variable, each helper will be tried in turn, and may provide a username, password, or nothing. Once Git has acquired both a username and a password, no more helpers will be tried. +If `credential.helper` is configured to the empty string, this resets +the helper list to empty (so you may override a helper set by a +lower-priority config file by configuring the empty-string helper, +followed by whatever set of helpers you would like). + CREDENTIAL CONTEXTS ------------------- diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index f08e9b80c5..dfb43d000f 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -114,3 +114,11 @@ ifndef::git-pull[] reporting. endif::git-pull[] + +--allow-unrelated-histories:: + By default, `git merge` command refuses to merge histories + that do not share a common ancestor. This option can be + used to override this safety when merging histories of two + projects that started their lives independently. As that is + a very rare occasion, no configuration variable to enable + this by default exists and will not be added. diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 54b88b6dca..6c67182728 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -42,6 +42,20 @@ people using 80-column terminals. verbatim; this means that invalid sequences in the original commit may be copied to the output. +--expand-tabs=:: +--expand-tabs:: +--no-expand-tabs:: + Perform a tab expansion (replace each tab with enough spaces + to fill to the next display column that is multiple of '') + in the log message before showing it in the output. + `--expand-tabs` is a short-hand for `--expand-tabs=8`, and + `--no-expand-tabs` is a short-hand for `--expand-tabs=0`, + which disables tab expansion. ++ +By default, tabs are expanded in pretty formats that indent the log +message by 4 spaces (i.e. 'medium', which is the default, 'full', +and 'fuller'). + ifndef::git-rev-list[] --notes[=]:: Show the notes (see linkgit:git-notes[1]) that annotate the diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt index 0d8b99b368..20741f345e 100644 --- a/Documentation/technical/api-config.txt +++ b/Documentation/technical/api-config.txt @@ -63,13 +63,6 @@ parse for configuration, rather than looking in the usual files. Regular Specify whether include directives should be followed in parsed files. Regular `git_config` defaults to `1`. -There is a special version of `git_config` called `git_config_early`. -This version takes an additional parameter to specify the repository -config, instead of having it looked up via `git_path`. This is useful -early in a Git program before the repository has been found. Unless -you're working with early setup code, you probably don't want to use -this. - Reading Specific Files ---------------------- diff --git a/Documentation/technical/api-trace.txt b/Documentation/technical/api-trace.txt index 389ae16d15..fadb5979c4 100644 --- a/Documentation/technical/api-trace.txt +++ b/Documentation/technical/api-trace.txt @@ -28,7 +28,7 @@ static struct trace_key trace_foo = TRACE_KEY_INIT(FOO); static void trace_print_foo(const char *message) { - trace_print_key(&trace_foo, message); + trace_printf_key(&trace_foo, "%s", message); } ------------ + diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 46595dad22..655b49011f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.8.1 +DEF_VER=v2.8.0.GIT LF=' ' diff --git a/Makefile b/Makefile index 2742a6977c..6c4e2dbbc8 100644 --- a/Makefile +++ b/Makefile @@ -355,9 +355,6 @@ all:: # # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt. # -# Define NO_HMAC_CTX_CLEANUP if your OpenSSL is version 0.9.6b or earlier to -# cleanup the HMAC context with the older HMAC_cleanup function. -# # Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily # compiles the following initialization: # @@ -624,7 +621,7 @@ TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-urlmatch-normalization TEST_PROGRAMS_NEED_X += test-wildmatch -TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) +TEST_PROGRAMS = $(patsubst %,t/helper/%$X,$(TEST_PROGRAMS_NEED_X)) # List built-in command $C whose implementation cmd_$C() is not in # builtin/$C.o but is linked in as part of some other command. @@ -1138,9 +1135,6 @@ ifndef NO_OPENSSL ifdef NEEDS_CRYPTO_WITH_SSL OPENSSL_LIBSSL += -lcrypto endif - ifdef NO_HMAC_CTX_CLEANUP - BASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP - endif else BASIC_CFLAGS += -DNO_OPENSSL BLK_SHA1 = 1 @@ -1904,7 +1898,7 @@ VCSSVN_OBJS += vcs-svn/fast_export.o VCSSVN_OBJS += vcs-svn/svndiff.o VCSSVN_OBJS += vcs-svn/svndump.o -TEST_OBJS := $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) +TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ $(XDIFF_OBJS) \ $(VCSSVN_OBJS) \ @@ -2211,7 +2205,7 @@ bin-wrappers/%: wrap-for-bin.sh @mkdir -p bin-wrappers $(QUIET_GEN)sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@@BUILD_DIR@@|$(shell pwd)|' \ - -e 's|@@PROG@@|$(@F)|' < $< > $@ && \ + -e 's|@@PROG@@|$(patsubst test-%,t/helper/test-%,$(@F))|' < $< > $@ && \ chmod +x $@ # GNU make supports exporting all variables by "export" without parameters. @@ -2231,25 +2225,25 @@ perf: all .PHONY: test perf -test-ctype$X: ctype.o +t/helper/test-ctype$X: ctype.o -test-date$X: date.o ctype.o +t/helper/test-date$X: date.o ctype.o -test-delta$X: diff-delta.o patch-delta.o +t/helper/test-delta$X: diff-delta.o patch-delta.o -test-line-buffer$X: vcs-svn/lib.a +t/helper/test-line-buffer$X: vcs-svn/lib.a -test-parse-options$X: parse-options.o parse-options-cb.o +t/helper/test-parse-options$X: parse-options.o parse-options-cb.o -test-svn-fe$X: vcs-svn/lib.a +t/helper/test-svn-fe$X: vcs-svn/lib.a .PRECIOUS: $(TEST_OBJS) -test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS) +t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) -check-sha1:: test-sha1$X - ./test-sha1.sh +check-sha1:: t/helper/test-sha1$X + t/helper/test-sha1.sh SP_OBJ = $(patsubst %.o,%.sp,$(C_OBJ)) @@ -2263,10 +2257,10 @@ sparse: $(SP_OBJ) check: common-cmds.h @if sparse; \ then \ - echo 2>&1 "Use 'make sparse' instead"; \ + echo >&2 "Use 'make sparse' instead"; \ $(MAKE) --no-print-directory sparse; \ else \ - echo 2>&1 "Did you mean 'make test'?"; \ + echo >&2 "Did you mean 'make test'?"; \ exit 1; \ fi @@ -2456,8 +2450,8 @@ profile-clean: $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) clean: profile-clean coverage-clean - $(RM) *.o *.res refs/*.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o - $(RM) xdiff/*.o vcs-svn/*.o ewah/*.o builtin/*.o + $(RM) *.res + $(RM) $(OBJECTS) $(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(NO_INSTALL) diff --git a/README.md b/README.md index d1ffbb6170..2087748f0c 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ including full documentation and Git related tools. See [Documentation/gittutorial.txt][] to get started, then see [Documentation/giteveryday.txt][] for a useful minimum set of commands, and -[Documentation/git-commandname.txt][] for documentation of each command. +Documentation/git-*commandname*.txt for documentation of each command. If git has been correctly installed, then the tutorial can also be read with "man gittutorial" or "git help tutorial", and the -documentation of each command with "man git-commandname" or "git help -commandname". +documentation of each command with "man git-*commandname*" or "git help +*commandname*". CVS users may also want to read [Documentation/gitcvs-migration.txt][] ("man gitcvs-migration" or "git help cvs-migration" if git is @@ -57,6 +57,5 @@ and the name as (depending on your mood): [INSTALL]: INSTALL [Documentation/gittutorial.txt]: Documentation/gittutorial.txt [Documentation/giteveryday.txt]: Documentation/giteveryday.txt -[Documentation/git-commandname.txt]: Documentation/git-commandname.txt [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt [Documentation/SubmittingPatches]: Documentation/SubmittingPatches diff --git a/RelNotes b/RelNotes index d40c3e126c..66606735cb 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.8.1.txt \ No newline at end of file +Documentation/RelNotes/2.9.0.txt \ No newline at end of file diff --git a/abspath.c b/abspath.c index 5edb4e7816..2825de8591 100644 --- a/abspath.c +++ b/abspath.c @@ -167,7 +167,6 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); #else - char *p; /* don't add prefix to absolute paths, but still replace '\' by '/' */ strbuf_reset(&path); if (is_absolute_path(arg)) @@ -175,9 +174,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) else if (pfx_len) strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); - for (p = path.buf + pfx_len; *p; p++) - if (*p == '\\') - *p = '/'; + convert_slashes(path.buf + pfx_len); #endif return path.buf; } diff --git a/branch.c b/branch.c index c50ea42172..4162443707 100644 --- a/branch.c +++ b/branch.c @@ -344,3 +344,26 @@ void die_if_checked_out(const char *branch) die(_("'%s' is already checked out at '%s'"), branch, existing); } } + +int replace_each_worktree_head_symref(const char *oldref, const char *newref) +{ + int ret = 0; + struct worktree **worktrees = get_worktrees(); + int i; + + for (i = 0; worktrees[i]; i++) { + if (worktrees[i]->is_detached) + continue; + if (strcmp(oldref, worktrees[i]->head_ref)) + continue; + + if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) { + ret = -1; + error(_("HEAD of working tree %s is not updated"), + worktrees[i]->path); + } + } + + free_worktrees(worktrees); + return ret; +} diff --git a/branch.h b/branch.h index 78ad4387cd..d69163daf7 100644 --- a/branch.h +++ b/branch.h @@ -60,4 +60,11 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name); */ extern void die_if_checked_out(const char *branch); +/* + * Update all per-worktree HEADs pointing at the old ref to point the new ref. + * This will be used when renaming a branch. Returns 0 if successful, non-zero + * otherwise. + */ +extern int replace_each_worktree_head_symref(const char *oldref, const char *newref); + #endif diff --git a/builtin/apply.c b/builtin/apply.c index 42c610e2ec..8e4da2e1bd 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -931,22 +931,19 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, return find_name(line, NULL, p_value, TERM_TAB); if (orig_name) { - int len; - const char *name; + int len = strlen(orig_name); char *another; - name = orig_name; - len = strlen(name); if (isnull) - die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr); + die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), + orig_name, linenr); another = find_name(line, NULL, p_value, TERM_TAB); - if (!another || memcmp(another, name, len + 1)) + if (!another || memcmp(another, orig_name, len + 1)) die((side == DIFF_NEW_NAME) ? _("git apply: bad git-diff - inconsistent new filename on line %d") : _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr); free(another); return orig_name; - } - else { + } else { /* expect "/dev/null" */ if (memcmp("/dev/null", line, 9) || line[9] != '\n') die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr); @@ -956,21 +953,15 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, static int gitdiff_oldname(const char *line, struct patch *patch) { - char *orig = patch->old_name; patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, DIFF_OLD_NAME); - if (orig != patch->old_name) - free(orig); return 0; } static int gitdiff_newname(const char *line, struct patch *patch) { - char *orig = patch->new_name; patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, DIFF_NEW_NAME); - if (orig != patch->new_name) - free(orig); return 0; } @@ -1872,6 +1863,11 @@ static struct fragment *parse_binary_hunk(char **buf_p, return NULL; } +/* + * Returns: + * -1 in case of error, + * the length of the parsed binary patch otherwise + */ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) { /* @@ -2017,6 +2013,8 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) linenr++; used = parse_binary(buffer + hd + llen, size - hd - llen, patch); + if (used < 0) + return -1; if (used) patchsize = used + llen; else @@ -4373,8 +4371,10 @@ static int apply_patch(int fd, const char *filename, int options) patch->inaccurate_eof = !!(options & INACCURATE_EOF); patch->recount = !!(options & RECOUNT); nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); - if (nr < 0) + if (nr < 0) { + free_patch(patch); break; + } if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { @@ -4383,6 +4383,8 @@ static int apply_patch(int fd, const char *filename, int options) listp = &patch->next; } else { + if (apply_verbosely) + say_patch_name(stderr, _("Skipped patch '%s'."), patch); free_patch(patch); skipped_patch++; } diff --git a/builtin/blame.c b/builtin/blame.c index e982fb8137..21f42b0b62 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2307,6 +2307,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, unsigned mode; struct strbuf msg = STRBUF_INIT; + read_cache(); time(&now); commit = alloc_commit_node(); commit->object.parsed = 1; diff --git a/builtin/branch.c b/builtin/branch.c index 7b45b6bd6b..0adba629d2 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -20,6 +20,7 @@ #include "utf8.h" #include "wt-status.h" #include "ref-filter.h" +#include "worktree.h" static const char * const builtin_branch_usage[] = { N_("git branch [] [-r | -a] [--merged | --no-merged]"), @@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { - error(_("Cannot delete the branch '%s' " - "which you are currently on."), bname.buf); - ret = 1; - continue; - } - free(name); - name = mkpathdup(fmt, bname.buf); + + if (kinds == FILTER_REFS_BRANCHES) { + char *worktree = find_shared_symref("HEAD", name); + if (worktree) { + error(_("Cannot delete branch '%s' " + "checked out at '%s'"), + bname.buf, worktree); + free(worktree); + ret = 1; + continue; + } + } + target = resolve_ref_unsafe(name, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE @@ -393,22 +399,25 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, int current = 0; int color; struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; - const char *prefix = ""; + const char *prefix_to_show = ""; + const char *prefix_to_skip = NULL; const char *desc = item->refname; char *to_free = NULL; switch (item->kind) { case FILTER_REFS_BRANCHES: - skip_prefix(desc, "refs/heads/", &desc); + prefix_to_skip = "refs/heads/"; + skip_prefix(desc, prefix_to_skip, &desc); if (!filter->detached && !strcmp(desc, head)) current = 1; else color = BRANCH_COLOR_LOCAL; break; case FILTER_REFS_REMOTES: - skip_prefix(desc, "refs/remotes/", &desc); + prefix_to_skip = "refs/remotes/"; + skip_prefix(desc, prefix_to_skip, &desc); color = BRANCH_COLOR_REMOTE; - prefix = remote_prefix; + prefix_to_show = remote_prefix; break; case FILTER_REFS_DETACHED_HEAD: desc = to_free = get_head_description(); @@ -425,7 +434,7 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, color = BRANCH_COLOR_CURRENT; } - strbuf_addf(&name, "%s%s", prefix, desc); + strbuf_addf(&name, "%s%s", prefix_to_show, desc); if (filter->verbose) { int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf); strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), @@ -436,8 +445,10 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth, name.buf, branch_get_color(BRANCH_COLOR_RESET)); if (item->symref) { - skip_prefix(item->symref, "refs/remotes/", &desc); - strbuf_addf(&out, " -> %s", desc); + const char *symref = item->symref; + if (prefix_to_skip) + skip_prefix(symref, prefix_to_skip, &symref); + strbuf_addf(&out, " -> %s", symref); } else if (filter->verbose) /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */ @@ -552,8 +563,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) if (recovery) warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); - /* no need to pass logmsg here as HEAD didn't really move */ - if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) + if (replace_each_worktree_head_symref(oldref.buf, newref.buf)) die(_("Branch renamed to %s, but HEAD is not updated!"), newname); strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11); diff --git a/builtin/checkout.c b/builtin/checkout.c index efcbd8f6b5..ea2fe1cf3f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -242,7 +242,6 @@ static int checkout_paths(const struct checkout_opts *opts, struct checkout state; static char *ps_matched; unsigned char rev[20]; - int flag; struct commit *head; int errs = 0; struct lock_file *lock_file; @@ -375,7 +374,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", 0, rev, &flag); + read_ref_full("HEAD", 0, rev, NULL); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); diff --git a/builtin/clone.c b/builtin/clone.c index 661639255c..6576ecf343 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -51,6 +51,7 @@ static enum transport_family family; static struct string_list option_config; static struct string_list option_reference; static int option_dissociate; +static int max_jobs = -1; static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -73,6 +74,8 @@ static struct option builtin_clone_options[] = { N_("initialize submodules in the clone")), OPT_BOOL(0, "recurse-submodules", &option_recursive, N_("initialize submodules in the clone")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), @@ -100,10 +103,6 @@ static struct option builtin_clone_options[] = { OPT_END() }; -static const char *argv_submodule[] = { - "submodule", "update", "--init", "--recursive", NULL -}; - static const char *get_repo_path_1(struct strbuf *path, int *is_bundle) { static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; @@ -732,8 +731,16 @@ static int checkout(void) err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(sha1), "1", NULL); - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); + if (!err && option_recursive) { + struct argv_array args = ARGV_ARRAY_INIT; + argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + + if (max_jobs != -1) + argv_array_pushf(&args, "--jobs=%d", max_jobs); + + err = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + } return err; } diff --git a/builtin/commit.c b/builtin/commit.c index c733ec98b7..391126e58d 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -186,6 +186,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn) gitmodules_config(); git_config(fn, s); determine_whence(s); + init_diff_ui_defaults(); s->hints = advice_status_hints; /* must come after git_config() */ } @@ -694,7 +695,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } } - if (message.len) { + if (have_option_m) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { @@ -1171,9 +1172,9 @@ static int parse_and_validate_options(int argc, const char *argv[], f++; if (f > 1) die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (message.len && f > 0) + if (have_option_m && f > 0) die((_("Option -m cannot be combined with -c/-C/-F/--fixup."))); - if (f || message.len) + if (f || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; diff --git a/builtin/diff.c b/builtin/diff.c index 52c98a9217..343c6b8f25 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -318,6 +318,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (!no_index) gitmodules_config(); + init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); diff --git a/builtin/fetch.c b/builtin/fetch.c index e4639d8eb1..f8455bde7a 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -37,7 +37,7 @@ static int prune = -1; /* unspecified */ static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT, unshallow, update_shallow; -static int max_children = 1; +static int max_children = -1; static enum transport_family family; static const char *depth; static const char *upload_pack; diff --git a/builtin/fsck.c b/builtin/fsck.c index 55eac756f7..3f27456883 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -493,13 +493,12 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { - int flag; int null_is_error = 0; if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag); + head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL); if (!head_points_at) { errors_found |= ERROR_REFS; return error("Invalid HEAD"); diff --git a/builtin/init-db.c b/builtin/init-db.c index da531f6b76..b2d8d40a67 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -95,6 +95,8 @@ static void copy_templates(const char *template_dir) struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; size_t template_len; + struct repository_format template_format; + struct strbuf err = STRBUF_INIT; DIR *dir; char *to_free = NULL; @@ -121,17 +123,18 @@ static void copy_templates(const char *template_dir) /* Make sure that template is from the correct vintage */ strbuf_addstr(&template_path, "config"); - repository_format_version = 0; - git_config_from_file(check_repository_format_version, - template_path.buf, NULL); + read_repository_format(&template_format, template_path.buf); strbuf_setlen(&template_path, template_len); - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - warning(_("not copying templates of " - "a wrong format version %d from '%s'"), - repository_format_version, - template_dir); + /* + * No mention of version at all is OK, but anything else should be + * verified. + */ + if (template_format.version >= 0 && + verify_repository_format(&template_format, &err) < 0) { + warning(_("not copying templates from '%s': %s"), + template_dir, err.buf); + strbuf_release(&err); goto close_free_return; } @@ -199,13 +202,13 @@ static int create_default_files(const char *template_path) /* reading existing config may have overwrote it */ if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * We would have created the above under user's umask -- under * shared-repository settings, we would need to fix them up. */ - if (shared_repository) { + if (get_shared_repository()) { adjust_shared_perm(get_git_dir()); adjust_shared_perm(git_path_buf(&buf, "refs")); adjust_shared_perm(git_path_buf(&buf, "refs/heads")); @@ -370,7 +373,7 @@ int init_db(const char *template_dir, unsigned int flags) create_object_directory(); - if (shared_repository) { + if (get_shared_repository()) { char buf[10]; /* We do not spell "group" and such, so that * the configuration can be read by older version @@ -378,12 +381,12 @@ int init_db(const char *template_dir, unsigned int flags) * and compatibility values for PERM_GROUP and * PERM_EVERYBODY. */ - if (shared_repository < 0) + if (get_shared_repository() < 0) /* force to the mode value */ - xsnprintf(buf, sizeof(buf), "0%o", -shared_repository); - else if (shared_repository == PERM_GROUP) + xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository()); + else if (get_shared_repository() == PERM_GROUP) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); - else if (shared_repository == PERM_EVERYBODY) + else if (get_shared_repository() == PERM_EVERYBODY) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else die("BUG: invalid value for shared_repository"); @@ -399,7 +402,7 @@ int init_db(const char *template_dir, unsigned int flags) "", and the last '%s%s' is the verbatim directory name. */ printf(_("%s%s Git repository in %s%s\n"), reinit ? _("Reinitialized existing") : _("Initialized empty"), - shared_repository ? _(" shared") : "", + get_shared_repository() ? _(" shared") : "", git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } @@ -494,8 +497,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * and we know shared_repository should always be 0; * but just in case we play safe. */ - saved = shared_repository; - shared_repository = 0; + saved = get_shared_repository(); + set_shared_repository(0); switch (safe_create_leading_directories_const(argv[0])) { case SCLD_OK: case SCLD_PERMS: @@ -507,7 +510,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) die_errno(_("cannot mkdir %s"), argv[0]); break; } - shared_repository = saved; + set_shared_repository(saved); if (mkdir(argv[0], 0777) < 0) die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; @@ -525,7 +528,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR diff --git a/builtin/log.c b/builtin/log.c index 0d738d6ddc..dff3fbbb43 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -100,6 +100,12 @@ static int log_line_range_callback(const struct option *option, const char *arg, return 0; } +static void init_log_defaults(void) +{ + init_grep_defaults(); + init_diff_ui_defaults(); +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) @@ -416,7 +422,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -527,7 +533,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct pathspec match_all; int i, count, ret = 0; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); memset(&match_all, 0, sizeof(match_all)); @@ -608,7 +614,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -647,7 +653,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -1280,10 +1286,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; - init_grep_defaults(); + init_log_defaults(); git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; + rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; rev.max_parents = 1; diff --git a/builtin/merge.c b/builtin/merge.c index bf2f2614fb..b555a1bf9c 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -64,6 +64,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; static const char *sign_commit; @@ -221,6 +222,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, + N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, @@ -819,6 +822,14 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { unsigned char result_tree[20], result_commit[20]; struct commit_list *parents, **pptr = &parents; + static struct lock_file lock; + + hold_locked_index(&lock, 1); + refresh_cache(REFRESH_QUIET); + if (active_cache_changed && + write_locked_index(&the_index, &lock, COMMIT_LOCK)) + return error(_("Unable to write index.")); + rollback_lock_file(&lock); write_tree_trivial(result_tree); printf(_("Wonderful.\n")); @@ -1165,7 +1176,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, i, ret = 0, head_subsumed; + int i, ret = 0, head_subsumed; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; @@ -1179,7 +1190,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag); + branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL); if (branch && starts_with(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) @@ -1187,6 +1198,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else head_commit = lookup_commit_or_die(head_sha1, "HEAD"); + init_diff_ui_defaults(); git_config(git_merge_config, NULL); if (branch_mergeoptions) @@ -1397,9 +1409,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - if (remoteheads && !common) - ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads || + if (remoteheads && !common) { + /* No common ancestors found. */ + if (!allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + /* otherwise, we need a real merge. */ + } else if (!remoteheads || (!remoteheads->next && !common->next && common->item == remoteheads->item)) { /* diff --git a/builtin/mv.c b/builtin/mv.c index aeae855e2b..a2014266b6 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -252,15 +252,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int pos; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); - if (!show_only && mode != INDEX) { - if (rename(src, dst) < 0 && !ignore_errors) - die_errno(_("renaming '%s' failed"), src); - if (submodule_gitfile[i]) { - if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) - connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); - if (!update_path_in_gitmodules(src, dst)) - gitmodules_modified = 1; - } + if (show_only) + continue; + if (mode != INDEX && rename(src, dst) < 0) { + if (ignore_errors) + continue; + die_errno(_("renaming '%s' failed"), src); + } + if (submodule_gitfile[i]) { + if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR) + connect_work_tree_and_git_dir(dst, submodule_gitfile[i]); + if (!update_path_in_gitmodules(src, dst)) + gitmodules_modified = 1; } if (mode == WORKING_DIRECTORY) diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 092e03c3cc..57be35faf5 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -10,6 +10,7 @@ typedef struct rev_name { const char *tip_name; + unsigned long taggerdate; int generation; int distance; } rev_name; @@ -20,7 +21,8 @@ static long cutoff = LONG_MAX; #define MERGE_TRAVERSAL_WEIGHT 65535 static void name_rev(struct commit *commit, - const char *tip_name, int generation, int distance, + const char *tip_name, unsigned long taggerdate, + int generation, int distance, int deref) { struct rev_name *name = (struct rev_name *)commit->util; @@ -43,9 +45,12 @@ static void name_rev(struct commit *commit, name = xmalloc(sizeof(rev_name)); commit->util = name; goto copy_data; - } else if (name->distance > distance) { + } else if (name->taggerdate > taggerdate || + (name->taggerdate == taggerdate && + name->distance > distance)) { copy_data: name->tip_name = tip_name; + name->taggerdate = taggerdate; name->generation = generation; name->distance = distance; } else @@ -66,11 +71,11 @@ static void name_rev(struct commit *commit, new_name = xstrfmt("%.*s^%d", (int)len, tip_name, parent_number); - name_rev(parents->item, new_name, 0, + name_rev(parents->item, new_name, taggerdate, 0, distance + MERGE_TRAVERSAL_WEIGHT, 0); } else { - name_rev(parents->item, tip_name, generation + 1, - distance + 1, 0); + name_rev(parents->item, tip_name, taggerdate, + generation + 1, distance + 1, 0); } } } @@ -140,6 +145,7 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + unsigned long taggerdate = ULONG_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) return 0; @@ -164,12 +170,13 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo break; /* broken repository */ o = parse_object(t->tagged->oid.hash); deref = 1; + taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)o; path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), 0, 0, deref); + name_rev(commit, xstrdup(path), taggerdate, 0, 0, deref); } return 0; } diff --git a/builtin/notes.c b/builtin/notes.c index ed6f2222f4..6fd058de92 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -744,13 +744,14 @@ static int merge_commit(struct notes_merge_options *o) static int git_config_get_notes_strategy(const char *key, enum notes_merge_strategy *strategy) { - const char *value; + char *value; - if (git_config_get_string_const(key, &value)) + if (git_config_get_string(key, &value)) return 1; if (parse_notes_merge_strategy(value, strategy)) git_die_config(key, "unknown notes merge strategy %s", value); + free(value); return 0; } diff --git a/builtin/pull.c b/builtin/pull.c index 10eff03967..596b92fc56 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -86,9 +86,12 @@ static char *opt_commit; static char *opt_edit; static char *opt_ff; static char *opt_verify_signatures; +static int opt_autostash = -1; +static int config_autostash; static struct argv_array opt_strategies = ARGV_ARRAY_INIT; static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; static char *opt_gpg_sign; +static int opt_allow_unrelated_histories; /* Options passed to git-fetch */ static char *opt_all; @@ -149,6 +152,8 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, N_("verify that the named commit has a valid GPG signature"), PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after rebase")), OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), N_("merge strategy to use"), 0), @@ -159,6 +164,9 @@ static struct option pull_options[] = { OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG), + OPT_SET_INT(0, "allow-unrelated-histories", + &opt_allow_unrelated_histories, + N_("allow merging unrelated histories"), 1), /* Options passed to git-fetch */ OPT_GROUP(N_("Options related to fetching")), @@ -305,6 +313,18 @@ static enum rebase_type config_get_rebase(void) return REBASE_FALSE; } +/** + * Read config variables. + */ +static int git_pull_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "rebase.autostash")) { + config_autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + /** * Returns 1 if there are unstaged changes, 0 otherwise. */ @@ -612,6 +632,8 @@ static int run_merge(void) argv_array_pushv(&args, opt_strategy_opts.argv); if (opt_gpg_sign) argv_array_push(&args, opt_gpg_sign); + if (opt_allow_unrelated_histories > 0) + argv_array_push(&args, "--allow-unrelated-histories"); argv_array_push(&args, "FETCH_HEAD"); ret = run_command_v_opt(args.argv, RUN_GIT_CMD); @@ -789,6 +811,10 @@ static int run_rebase(const unsigned char *curr_head, argv_array_pushv(&args, opt_strategy_opts.argv); if (opt_gpg_sign) argv_array_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + argv_array_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + argv_array_push(&args, "--autostash"); argv_array_push(&args, "--onto"); argv_array_push(&args, sha1_to_hex(merge_head)); @@ -823,7 +849,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase < 0) opt_rebase = config_get_rebase(); - git_config(git_default_config, NULL); + git_config(git_pull_config, NULL); if (read_cache_unmerged()) die_resolve_conflict("Pull"); @@ -834,13 +860,17 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_sha1("HEAD", orig_head)) hashclr(orig_head); + if (!opt_rebase && opt_autostash != -1) + die(_("--[no-]autostash option is only valid with --rebase.")); + if (opt_rebase) { - int autostash = 0; + int autostash = config_autostash; + if (opt_autostash != -1) + autostash = opt_autostash; if (is_null_sha1(orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - git_config_get_bool("rebase.autostash", &autostash); if (!autostash) die_on_unclean_work_tree(prefix); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c8e32b297c..a744437b58 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -21,7 +21,10 @@ #include "sigchain.h" #include "fsck.h" -static const char receive_pack_usage[] = "git receive-pack "; +static const char * const receive_pack_usage[] = { + N_("git receive-pack "), + NULL +}; enum deny_action { DENY_UNCONFIGURED, @@ -49,7 +52,7 @@ static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; -static int fix_thin = 1; +static int reject_thin; static int stateless_rpc; static const char *service_dir; static const char *head_name; @@ -1081,13 +1084,13 @@ static void check_aliased_update(struct command *cmd, struct string_list *list) if (!(flag & REF_ISSYMREF)) return; - dst_name = strip_namespace(dst_name); if (!dst_name) { rp_error("refusing update to broken symref '%s'", cmd->ref_name); cmd->skip_update = 1; cmd->error_string = "broken symref"; return; } + dst_name = strip_namespace(dst_name); if ((item = string_list_lookup(list, dst_name)) == NULL) return; @@ -1548,7 +1551,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (fsck_objects) argv_array_pushf(&child.args, "--strict%s", fsck_msg_types.buf); - if (fix_thin) + if (!reject_thin) argv_array_push(&child.args, "--fix-thin"); child.out = -1; child.err = err_fd; @@ -1707,45 +1710,29 @@ static int delete_only(struct command *commands) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; - int i; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; struct shallow_info si; - packet_trace_identity("receive-pack"); + struct option options[] = { + OPT__QUIET(&quiet, N_("quiet")), + OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL), + OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL), + OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL), + OPT_END() + }; - argv++; - for (i = 1; i < argc; i++) { - const char *arg = *argv++; + packet_trace_identity("receive-pack"); - if (*arg == '-') { - if (!strcmp(arg, "--quiet")) { - quiet = 1; - continue; - } + argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); - if (!strcmp(arg, "--advertise-refs")) { - advertise_refs = 1; - continue; - } - if (!strcmp(arg, "--stateless-rpc")) { - stateless_rpc = 1; - continue; - } - if (!strcmp(arg, "--reject-thin-pack-for-testing")) { - fix_thin = 0; - continue; - } + if (argc > 1) + usage_msg_opt(_("Too many arguments."), receive_pack_usage, options); + if (argc == 0) + usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options); - usage(receive_pack_usage); - } - if (service_dir) - usage(receive_pack_usage); - service_dir = arg; - } - if (!service_dir) - usage(receive_pack_usage); + service_dir = argv[0]; setup_path(); diff --git a/builtin/replace.c b/builtin/replace.c index 748c6ca954..b58c714cb8 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -440,6 +440,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix) }; check_replace_refs = 0; + git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 5b9dd6a9d8..1ff5a67538 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -225,7 +225,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) * --all and --mirror are incompatible; neither makes sense * with any refspecs. */ - if ((refspecs && (send_all || args.send_mirror)) || + if ((nr_refspecs > 0 && (send_all || args.send_mirror)) || (send_all && args.send_mirror)) usage_with_options(send_pack_usage, options); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 5295b727d4..3bd6883eff 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -118,6 +118,55 @@ static int module_name(int argc, const char **argv, const char *prefix) return 0; } + +/* + * 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) +{ + if (starts_with(var, "credential.")) + return 1; + return 0; +} + +static int sanitize_submodule_config(const char *var, const char *value, void *data) +{ + struct strbuf *out = data; + + if (submodule_config_ok(var)) { + if (out->len) + strbuf_addch(out, ' '); + + if (value) + sq_quotef(out, "%s=%s", var, value); + else + sq_quote_buf(out, var); + } + + return 0; +} + +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); + } + } + +} + static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, const char *reference, int quiet) { @@ -139,7 +188,7 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url argv_array_push(&cp.args, path); cp.git_cmd = 1; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.no_stdin = 1; return run_command(&cp); @@ -147,11 +196,11 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url static int module_clone(int argc, const char **argv, const char *prefix) { - const char *path = NULL, *name = NULL, *url = NULL; + const char *name = NULL, *url = NULL; const char *reference = NULL, *depth = NULL; int quiet = 0; FILE *submodule_dot_git; - char *sm_gitdir, *cwd, *p; + char *p, *path = NULL, *sm_gitdir; struct strbuf rel_path = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; @@ -180,16 +229,27 @@ static int module_clone(int argc, const char **argv, const char *prefix) const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=] [--quiet] " - "[--reference ] [--name ] [--url ]" - "[--depth ] [--] [...]"), + "[--reference ] [--name ] [--depth ] " + "--url --path "), NULL }; argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); + if (argc || !url || !path || !*path) + usage_with_options(git_submodule_helper_usage, + module_clone_options); + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); - sm_gitdir = strbuf_detach(&sb, NULL); + sm_gitdir = xstrdup(absolute_path(sb.buf)); + strbuf_reset(&sb); + + if (!is_absolute_path(path)) { + strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); + path = strbuf_detach(&sb, NULL); + } else + path = xstrdup(path); if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) @@ -206,49 +266,301 @@ static int module_clone(int argc, const char **argv, const char *prefix) } /* Write a .git file in the submodule to redirect to the superproject. */ - if (safe_create_leading_directories_const(path) < 0) - die(_("could not create directory '%s'"), path); - - if (path && *path) - strbuf_addf(&sb, "%s/.git", path); - else - strbuf_addstr(&sb, ".git"); - + strbuf_addf(&sb, "%s/.git", path); if (safe_create_leading_directories_const(sb.buf) < 0) die(_("could not create leading directories of '%s'"), sb.buf); submodule_dot_git = fopen(sb.buf, "w"); if (!submodule_dot_git) die_errno(_("cannot open file '%s'"), sb.buf); - fprintf(submodule_dot_git, "gitdir: %s\n", - relative_path(sm_gitdir, path, &rel_path)); + fprintf_or_die(submodule_dot_git, "gitdir: %s\n", + relative_path(sm_gitdir, path, &rel_path)); if (fclose(submodule_dot_git)) die(_("could not close file %s"), sb.buf); strbuf_reset(&sb); strbuf_reset(&rel_path); - cwd = xgetcwd(); /* Redirect the worktree of the submodule in the superproject's config */ - if (!is_absolute_path(sm_gitdir)) { - strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir); - free(sm_gitdir); - sm_gitdir = strbuf_detach(&sb, NULL); - } - - strbuf_addf(&sb, "%s/%s", cwd, path); p = git_pathdup_submodule(path, "config"); if (!p) die(_("could not get submodule directory for '%s'"), path); git_config_set_in_file(p, "core.worktree", - relative_path(sb.buf, sm_gitdir, &rel_path)); + relative_path(path, sm_gitdir, &rel_path)); strbuf_release(&sb); strbuf_release(&rel_path); free(sm_gitdir); - free(cwd); + free(path); free(p); 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; + struct module_list list; + unsigned warn_if_uninitialized : 1; + + /* update parameter passed via commandline */ + struct submodule_update_strategy update; + + /* configuration parameters which are passed on to the children */ + int quiet; + const char *reference; + const char *depth; + const char *recursive_prefix; + const char *prefix; + + /* Machine-readable status lines to be consumed by git-submodule.sh */ + struct string_list projectlines; + + /* If we want to stop as fast as possible and return an error */ + unsigned quickstop : 1; +}; +#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ + SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \ + STRING_LIST_INIT_DUP, 0} + +/** + * 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. + */ +static int prepare_to_clone_next_submodule(const struct cache_entry *ce, + struct child_process *child, + struct submodule_update_clone *suc, + struct strbuf *out) +{ + const struct submodule *sub = NULL; + struct strbuf displaypath_sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *displaypath = NULL; + char *url = NULL; + int needs_cloning = 0; + + if (ce_stage(ce)) { + if (suc->recursive_prefix) + strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name); + else + strbuf_addf(&sb, "%s", ce->name); + strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addch(out, '\n'); + goto cleanup; + } + + sub = submodule_from_path(null_sha1, ce->name); + + if (suc->recursive_prefix) + displaypath = relative_path(suc->recursive_prefix, + ce->name, &displaypath_sb); + else + displaypath = ce->name; + + if (suc->update.type == SM_UPDATE_NONE + || (suc->update.type == SM_UPDATE_UNSPECIFIED + && sub->update_strategy.type == SM_UPDATE_NONE)) { + strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); + strbuf_addch(out, '\n'); + goto cleanup; + } + + /* + * Looking up the url in .git/config. + * We must not fall back to .gitmodules as we only want + * to process configured submodules. + */ + strbuf_reset(&sb); + 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'); + } + goto cleanup; + } + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/.git", ce->name); + needs_cloning = !file_exists(sb.buf); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, + sha1_to_hex(ce->sha1), ce_stage(ce), + needs_cloning, ce->name); + string_list_append(&suc->projectlines, sb.buf); + + if (!needs_cloning) + goto cleanup; + + child->git_cmd = 1; + child->no_stdin = 1; + child->stdout_to_stderr = 1; + child->err = -1; + argv_array_push(&child->args, "submodule--helper"); + argv_array_push(&child->args, "clone"); + if (suc->quiet) + argv_array_push(&child->args, "--quiet"); + if (suc->prefix) + argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); + argv_array_pushl(&child->args, "--path", sub->path, NULL); + argv_array_pushl(&child->args, "--name", sub->name, NULL); + argv_array_pushl(&child->args, "--url", url, NULL); + if (suc->reference) + argv_array_push(&child->args, suc->reference); + if (suc->depth) + argv_array_push(&child->args, suc->depth); + +cleanup: + free(url); + strbuf_reset(&displaypath_sb); + strbuf_reset(&sb); + + return needs_cloning; +} + +static int update_clone_get_next_task(struct child_process *child, + struct strbuf *err, + void *suc_cb, + void **void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + + for (; suc->current < suc->list.nr; suc->current++) { + const struct cache_entry *ce = suc->list.entries[suc->current]; + if (prepare_to_clone_next_submodule(ce, child, suc, err)) { + suc->current++; + return 1; + } + } + return 0; +} + +static int update_clone_start_failure(struct strbuf *err, + void *suc_cb, + void *void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + suc->quickstop = 1; + return 1; +} + +static int update_clone_task_finished(int result, + struct strbuf *err, + void *suc_cb, + void *void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + + if (!result) + return 0; + + suc->quickstop = 1; + return 1; +} + +static int update_clone(int argc, const char **argv, const char *prefix) +{ + const char *update = NULL; + int max_jobs = -1; + struct string_list_item *item; + struct pathspec pathspec; + struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + + struct option module_update_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix, + N_("path"), + N_("path into the working tree, across nested " + "submodule boundaries")), + OPT_STRING(0, "update", &update, + N_("string"), + N_("rebase, merge, checkout or none")), + OPT_STRING(0, "reference", &suc.reference, N_("repo"), + N_("reference repository")), + OPT_STRING(0, "depth", &suc.depth, "", + N_("Create a shallow clone truncated to the " + "specified number of revisions")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("parallel jobs")), + OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper update_clone [--prefix=] [...]"), + NULL + }; + suc.prefix = prefix; + + argc = parse_options(argc, argv, prefix, module_update_clone_options, + git_submodule_helper_usage, 0); + + if (update) + if (parse_submodule_update_strategy(update, &suc.update) < 0) + die(_("bad value for update parameter")); + + if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) + return 1; + + if (pathspec.nr) + suc.warn_if_uninitialized = 1; + + /* Overlay the parsed .gitmodules file with .git/config */ + gitmodules_config(); + git_config(submodule_config, NULL); + + if (max_jobs < 0) + max_jobs = parallel_submodules(); + + run_processes_parallel(max_jobs, + update_clone_get_next_task, + update_clone_start_failure, + update_clone_task_finished, + &suc); + + /* + * We saved the output and put it out all at once now. + * That means: + * - the listener does not have to interleave their (checkout) + * work with our fetching. The writes involved in a + * checkout involve more straightforward sequential I/O. + * - the listener can avoid doing any work if fetching failed. + */ + if (suc.quickstop) + return 1; + + for_each_string_list_item(item, &suc.projectlines) + utf8_fprintf(stdout, "%s", item->string); + + return 0; +} + struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); @@ -258,19 +570,21 @@ static struct cmd_struct commands[] = { {"list", module_list}, {"name", module_name}, {"clone", module_clone}, + {"sanitize-config", module_sanitize_config}, + {"update-clone", update_clone} }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; if (argc < 2) - die(_("fatal: submodule--helper subcommand must be " + die(_("submodule--helper subcommand must be " "called with a subcommand")); for (i = 0; i < ARRAY_SIZE(commands); i++) if (!strcmp(argv[1], commands[i].cmd)) return commands[i].fn(argc - 1, argv + 1, prefix); - die(_("fatal: '%s' is not a valid submodule--helper " + die(_("'%s' is not a valid submodule--helper " "subcommand"), argv[1]); } diff --git a/builtin/tag.c b/builtin/tag.c index 1705c94665..50e4ae5678 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -29,6 +29,7 @@ static const char * const git_tag_usage[] = { }; static unsigned int colopts; +static int force_sign_annotate; static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { @@ -104,13 +105,7 @@ static int delete_tag(const char *name, const char *ref, static int verify_tag(const char *name, const char *ref, const unsigned char *sha1) { - const char *argv_verify_tag[] = {"verify-tag", - "-v", "SHA1_HEX", NULL}; - argv_verify_tag[2] = sha1_to_hex(sha1); - - if (run_command_v_opt(argv_verify_tag, RUN_GIT_CMD)) - return error(_("could not verify the tag '%s'"), name); - return 0; + return gpg_verify_tag(sha1, name, GPG_VERIFY_VERBOSE); } static int do_sign(struct strbuf *buffer) @@ -166,6 +161,11 @@ static int git_tag_config(const char *var, const char *value, void *cb) status = git_gpg_config(var, value, cb); if (status) return status; + if (!strcmp(var, "tag.forcesignannotated")) { + force_sign_annotate = git_config_bool(var, value); + return 0; + } + if (starts_with(var, "column.")) return git_column_config(var, value, "tag", &colopts); return git_default_config(var, value, cb); @@ -327,7 +327,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) char *cleanup_arg = NULL; int create_reflog = 0; int annotate = 0, force = 0; - int cmdmode = 0; + int cmdmode = 0, create_tag_object = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct ref_transaction *transaction; @@ -385,12 +385,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) opt.sign = 1; set_signing_key(keyid); } - if (opt.sign) - annotate = 1; + create_tag_object = (opt.sign || annotate || msg.given || msgfile); + if (argc == 0 && !cmdmode) cmdmode = 'l'; - if ((annotate || msg.given || msgfile || force) && (cmdmode != 0)) + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); @@ -431,7 +431,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) die(_("only one -F or -m option is allowed.")); - annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { @@ -474,8 +473,11 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); - if (annotate) + if (create_tag_object) { + if (force_sign_annotate && !annotate) + opt.sign = 1; create_tag(object, tag, &buf, &opt, prev, object); + } transaction = ref_transaction_begin(&err); if (!transaction || diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 00663f6a30..99f8148cf7 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -18,55 +18,6 @@ static const char * const verify_tag_usage[] = { NULL }; -static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) -{ - struct signature_check sigc; - int len; - int ret; - - memset(&sigc, 0, sizeof(sigc)); - - len = parse_signature(buf, size); - - if (size == len) { - if (flags & GPG_VERIFY_VERBOSE) - write_in_full(1, buf, len); - return error("no signature found"); - } - - ret = check_signature(buf, len, buf + len, size - len, &sigc); - print_signature_buffer(&sigc, flags); - - signature_check_clear(&sigc); - return ret; -} - -static int verify_tag(const char *name, unsigned flags) -{ - enum object_type type; - unsigned char sha1[20]; - char *buf; - unsigned long size; - int ret; - - if (get_sha1(name, sha1)) - return error("tag '%s' not found.", name); - - type = sha1_object_info(sha1, NULL); - if (type != OBJ_TAG) - return error("%s: cannot verify a non-tag object of type %s.", - name, typename(type)); - - buf = read_sha1_file(sha1, &type, &size); - if (!buf) - return error("%s: unable to read file.", name); - - ret = run_gpg_verify(buf, size, flags); - - free(buf); - return ret; -} - static int git_verify_tag_config(const char *var, const char *value, void *cb) { int status = git_gpg_config(var, value, cb); @@ -95,11 +46,13 @@ int cmd_verify_tag(int argc, const char **argv, const char *prefix) if (verbose) flags |= GPG_VERIFY_VERBOSE; - /* sometimes the program was terminated because this signal - * was received in the process of writing the gpg input: */ - signal(SIGPIPE, SIG_IGN); - while (i < argc) - if (verify_tag(argv[i++], flags)) + while (i < argc) { + unsigned char sha1[20]; + const char *name = argv[i++]; + if (get_sha1(name, sha1)) + had_error = !!error("tag '%s' not found.", name); + else if (gpg_verify_tag(sha1, name, flags)) had_error = 1; + } return had_error; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 38b56096bd..d8e3795dc4 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -21,6 +21,7 @@ static const char * const worktree_usage[] = { struct add_opts { int force; int detach; + int checkout; const char *new_branch; int force_new_branch; }; @@ -284,18 +285,22 @@ static int add_worktree(const char *path, const char *refname, if (ret) goto done; - cp.argv = NULL; - argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); - cp.env = child_env.argv; - ret = run_command(&cp); - if (!ret) { - is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; + if (opts->checkout) { + cp.argv = NULL; + argv_array_clear(&cp.args); + argv_array_pushl(&cp.args, "reset", "--hard", NULL); + cp.env = child_env.argv; + ret = run_command(&cp); + if (ret) + goto done; } + + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + done: strbuf_reset(&sb); strbuf_addf(&sb, "%s/locked", sb_repo.buf); @@ -320,10 +325,12 @@ static int add(int ac, const char **av, const char *prefix) OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), + OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_END() }; memset(&opts, 0, sizeof(opts)); + opts.checkout = 1; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) die(_("-b, -B, and --detach are mutually exclusive")); diff --git a/bundle.c b/bundle.c index 506ac49691..bbf4efa0a0 100644 --- a/bundle.c +++ b/bundle.c @@ -435,12 +435,14 @@ int create_bundle(struct bundle_header *header, const char *path, /* write prerequisites */ if (compute_and_write_prerequisites(bundle_fd, &revs, argc, argv)) - return -1; + goto err; argc = setup_revisions(argc, argv, &revs, NULL); - if (argc > 1) - return error(_("unrecognized argument: %s"), argv[1]); + if (argc > 1) { + error(_("unrecognized argument: %s"), argv[1]); + goto err; + } object_array_remove_duplicates(&revs.pending); @@ -448,17 +450,26 @@ int create_bundle(struct bundle_header *header, const char *path, if (!ref_count) die(_("Refusing to create empty bundle.")); else if (ref_count < 0) - return -1; + goto err; /* write pack */ - if (write_pack_data(bundle_fd, &revs)) - return -1; + if (write_pack_data(bundle_fd, &revs)) { + bundle_fd = -1; /* already closed by the above call */ + goto err; + } if (!bundle_to_stdout) { if (commit_lock_file(&lock)) die_errno(_("cannot create '%s'"), path); } return 0; +err: + if (!bundle_to_stdout) { + if (0 <= bundle_fd) + close(bundle_fd); + rollback_lock_file(&lock); + } + return -1; } int unbundle(struct bundle_header *header, int bundle_fd, int flags) diff --git a/cache.h b/cache.h index 9f09540bbc..fd728f0793 100644 --- a/cache.h +++ b/cache.h @@ -651,7 +651,6 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int warn_on_object_refname_ambiguity; -extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; extern const char *git_attributes_file; @@ -664,6 +663,9 @@ extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; +void set_shared_repository(int value); +int get_shared_repository(void); + /* * Do replace refs need to be checked this run? This variable is * initialized to true unless --no-replace-object is used or @@ -745,9 +747,39 @@ extern int grafts_replace_parents; */ #define GIT_REPO_VERSION 0 #define GIT_REPO_VERSION_READ 1 -extern int repository_format_version; extern int repository_format_precious_objects; -extern int check_repository_format(void); + +struct repository_format { + int version; + int precious_objects; + int is_bare; + char *work_tree; + struct string_list unknown_extensions; +}; + +/* + * Read the repository format characteristics from the config file "path" into + * "format" struct. Returns the numeric version. On error, -1 is returned, + * format->version is set to -1, and all other fields in the struct are + * undefined. + */ +int read_repository_format(struct repository_format *format, const char *path); + +/* + * Verify that the repository described by repository_format is something we + * can read. If it is, return 0. Otherwise, return -1, and "err" will describe + * any errors encountered. + */ +int verify_repository_format(const struct repository_format *format, + struct strbuf *err); + +/* + * Check the repository format version in the path found in get_git_dir(), + * and die if it is a version we don't understand. Generally one would + * set_git_dir() before calling this, and use it only for "are we in a valid + * repo?". + */ +extern void check_repository_format(void); #define MTIME_CHANGED 0x0001 #define CTIME_CHANGED 0x0002 @@ -926,8 +958,6 @@ static inline int is_empty_blob_sha1(const unsigned char *sha1) int git_mkstemp(char *path, size_t n, const char *template); -int git_mkstemps(char *path, size_t n, const char *template, int suffix_len); - /* set default permissions by passing mode arguments to open(2) */ int git_mkstemps_mode(char *pattern, int suffix_len, int mode); int git_mkstemp_mode(char *pattern, int mode); @@ -1526,7 +1556,6 @@ extern void git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, struct git_config_source *config_source, int respect_includes); -extern int git_config_early(config_fn_t fn, void *, const char *repo_config); extern int git_parse_ulong(const char *, unsigned long *); extern int git_parse_maybe_bool(const char *); extern int git_config_int(const char *, const char *); @@ -1550,7 +1579,6 @@ extern void git_config_set_multivar_in_file(const char *, const char *, const ch extern int git_config_rename_section(const char *, const char *); extern int git_config_rename_section_in_file(const char *, const char *, const char *); extern const char *git_etc_gitconfig(void); -extern int check_repository_format_version(const char *var, const char *value, void *cb); extern int git_env_bool(const char *, int); extern unsigned long git_env_ulong(const char *, unsigned long); extern int git_config_system(void); diff --git a/commit.h b/commit.h index 5d58be0017..b06db4d5d9 100644 --- a/commit.h +++ b/commit.h @@ -147,6 +147,7 @@ struct pretty_print_context { int preserve_subject; struct date_mode date_mode; unsigned date_mode_explicit:1; + int expand_tabs_in_log; int need_8bit_cte; char *notes_message; struct reflog_walk_info *reflog_info; diff --git a/compat/apple-common-crypto.h b/compat/apple-common-crypto.h index d3fb264181..11727f3e1e 100644 --- a/compat/apple-common-crypto.h +++ b/compat/apple-common-crypto.h @@ -3,12 +3,18 @@ #define HEADER_HMAC_H #define HEADER_SHA_H #include -#define HMAC_CTX CCHmacContext -#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len) -#define HMAC_Update CCHmacUpdate -#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash) -#define HMAC_CTX_cleanup(ignore) #define EVP_md5(...) kCCHmacAlgMD5 +/* CCHmac doesn't take md_len and the return type is void */ +#define HMAC git_CC_HMAC +static inline unsigned char *git_CC_HMAC(CCHmacAlgorithm alg, + const void *key, int key_len, + const unsigned char *data, size_t data_len, + unsigned char *md, unsigned int *md_len) +{ + CCHmac(alg, key, key_len, data, data_len, md); + return md; +} + #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070 #define APPLE_LION_OR_NEWER #include diff --git a/compat/mingw.c b/compat/mingw.c index 54c82ecf20..0413d5c3cd 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -763,15 +763,12 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) char *mingw_getcwd(char *pointer, int len) { - int i; wchar_t wpointer[MAX_PATH]; if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) return NULL; if (xwcstoutf(pointer, wpointer, len) < 0) return NULL; - for (i = 0; pointer[i]; i++) - if (pointer[i] == '\\') - pointer[i] = '/'; + convert_slashes(pointer); return pointer; } @@ -2112,9 +2109,7 @@ static void setup_windows_environment() * executable (by not mistaking the dir separators * for escape characters). */ - for (; *tmp; tmp++) - if (*tmp == '\\') - *tmp = '/'; + convert_slashes(tmp); } /* simulate TERM to enable auto-color (see color.c) */ diff --git a/compat/mingw.h b/compat/mingw.h index c008694639..1de70ffd62 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -406,7 +406,7 @@ static inline void convert_slashes(char *path) int mingw_offset_1st_component(const char *path); #define offset_1st_component mingw_offset_1st_component #define PATH_SEP ';' -#ifndef __MINGW64_VERSION_MAJOR +#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) #define PRIuMAX "I64u" #define PRId64 "I64d" #else diff --git a/compat/snprintf.c b/compat/snprintf.c index 42ea1ac110..0b11688537 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -9,7 +9,7 @@ * always have room for a trailing NUL byte. */ #ifndef SNPRINTF_SIZE_CORR -#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) +#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900) #define SNPRINTF_SIZE_CORR 1 #else #define SNPRINTF_SIZE_CORR 0 diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h index c65c2cd566..3a959d124c 100644 --- a/compat/vcbuild/include/unistd.h +++ b/compat/vcbuild/include/unistd.h @@ -45,11 +45,15 @@ typedef unsigned long long uintmax_t; typedef int64_t off64_t; +#if !defined(_MSC_VER) || _MSC_VER < 1600 #define INTMAX_MIN _I64_MIN #define INTMAX_MAX _I64_MAX #define UINTMAX_MAX _UI64_MAX #define UINT32_MAX 0xffffffff /* 4294967295U */ +#else +#include +#endif #define STDIN_FILENO 0 #define STDOUT_FILENO 1 diff --git a/compat/win32mmap.c b/compat/win32mmap.c index 80a8c9af4f..519d51f2b6 100644 --- a/compat/win32mmap.c +++ b/compat/win32mmap.c @@ -2,37 +2,42 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) { - HANDLE hmap; + HANDLE osfhandle, hmap; void *temp; - off_t len; - struct stat st; + LARGE_INTEGER len; uint64_t o = offset; uint32_t l = o & 0xFFFFFFFF; uint32_t h = (o >> 32) & 0xFFFFFFFF; - if (!fstat(fd, &st)) - len = st.st_size; - else + osfhandle = (HANDLE)_get_osfhandle(fd); + if (!GetFileSizeEx(osfhandle, &len)) die("mmap: could not determine filesize"); - if ((length + offset) > len) - length = xsize_t(len - offset); + if ((length + offset) > len.QuadPart) + length = xsize_t(len.QuadPart - offset); if (!(flags & MAP_PRIVATE)) die("Invalid usage of mmap when built with USE_WIN32_MMAP"); - hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL, - PAGE_WRITECOPY, 0, 0, NULL); + hmap = CreateFileMapping(osfhandle, NULL, + prot == PROT_READ ? PAGE_READONLY : PAGE_WRITECOPY, 0, 0, NULL); - if (!hmap) + if (!hmap) { + errno = EINVAL; return MAP_FAILED; + } - temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start); + temp = MapViewOfFileEx(hmap, prot == PROT_READ ? + FILE_MAP_READ : FILE_MAP_COPY, h, l, length, start); if (!CloseHandle(hmap)) warning("unable to close file mapping handle"); - return temp ? temp : MAP_FAILED; + if (temp) + return temp; + + errno = GetLastError() == ERROR_COMMITMENT_LIMIT ? EFBIG : EINVAL; + return MAP_FAILED; } int git_munmap(void *start, size_t length) diff --git a/config.c b/config.c index 9ba40bc1b0..10b5c957ae 100644 --- a/config.c +++ b/config.c @@ -108,7 +108,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc expanded = expand_user_path(path); if (!expanded) - return error("Could not expand include path '%s'", path); + return error("could not expand include path '%s'", path); path = expanded; /* @@ -162,7 +162,7 @@ void git_config_push_parameter(const char *text) { struct strbuf env = STRBUF_INIT; const char *old = getenv(CONFIG_DATA_ENVIRONMENT); - if (old) { + if (old && *old) { strbuf_addstr(&env, old); strbuf_addch(&env, ' '); } @@ -950,7 +950,7 @@ static int git_default_branch_config(const char *var, const char *value) else if (!strcmp(value, "always")) autorebase = AUTOREBASE_ALWAYS; else - return error("Malformed value for %s", var); + return error("malformed value for %s", var); return 0; } @@ -976,7 +976,7 @@ static int git_default_push_config(const char *var, const char *value) else if (!strcmp(value, "current")) push_default = PUSH_DEFAULT_CURRENT; else { - error("Malformed value for %s: %s", var, value); + error("malformed value for %s: %s", var, value); return error("Must be one of nothing, matching, simple, " "upstream or current."); } @@ -1188,11 +1188,12 @@ int git_config_system(void) return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); } -int git_config_early(config_fn_t fn, void *data, const char *repo_config) +static int do_git_config_sequence(config_fn_t fn, void *data) { int ret = 0, found = 0; char *xdg_config = xdg_config_home("config"); char *user_config = expand_user_path("~/.gitconfig"); + char *repo_config = git_pathdup("config"); if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) { ret += git_config_from_file(fn, git_etc_gitconfig(), @@ -1228,6 +1229,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) free(xdg_config); free(user_config); + free(repo_config); return ret == 0 ? found : ret; } @@ -1235,8 +1237,6 @@ int git_config_with_options(config_fn_t fn, void *data, struct git_config_source *config_source, int respect_includes) { - char *repo_config = NULL; - int ret; struct config_include_data inc = CONFIG_INCLUDE_INIT; if (respect_includes) { @@ -1257,11 +1257,7 @@ int git_config_with_options(config_fn_t fn, void *data, else if (config_source && config_source->blob) return git_config_from_blob_ref(fn, config_source->blob, data); - repo_config = git_pathdup("config"); - ret = git_config_early(fn, data, repo_config); - if (repo_config) - free(repo_config); - return ret; + return do_git_config_sequence(fn, data); } static void git_config_raw(config_fn_t fn, void *data) @@ -2221,9 +2217,13 @@ void git_config_set_multivar_in_file(const char *config_filename, const char *key, const char *value, const char *value_regex, int multi_replace) { - if (git_config_set_multivar_in_file_gently(config_filename, key, value, - value_regex, multi_replace) < 0) - die(_("Could not set '%s' to '%s'"), key, value); + if (!git_config_set_multivar_in_file_gently(config_filename, key, value, + value_regex, multi_replace)) + return; + if (value) + die(_("could not set '%s' to '%s'"), key, value); + else + die(_("could not unset '%s'"), key); } int git_config_set_multivar_gently(const char *key, const char *value, @@ -2404,7 +2404,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) #undef config_error_nonbool int config_error_nonbool(const char *var) { - return error("Missing value for '%s'", var); + return error("missing value for '%s'", var); } int parse_config_key(const char *var, diff --git a/config.mak.uname b/config.mak.uname index fe8096f8a6..40d6b29eee 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -187,6 +187,7 @@ ifeq ($(uname_O),Cygwin) X = .exe UNRELIABLE_FSTAT = UnfortunatelyYes SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease diff --git a/configure.ac b/configure.ac index 0cd9f4680b..c279025747 100644 --- a/configure.ac +++ b/configure.ac @@ -970,10 +970,6 @@ AC_CHECK_LIB([iconv], [locale_charset], [CHARSET_LIB=-lcharset])]) GIT_CONF_SUBST([CHARSET_LIB]) # -# Define NO_HMAC_CTX_CLEANUP=YesPlease if HMAC_CTX_cleanup is missing. -AC_CHECK_LIB([crypto], [HMAC_CTX_cleanup], - [], [GIT_CONF_SUBST([NO_HMAC_CTX_CLEANUP], [YesPlease])]) -# # Define HAVE_CLOCK_GETTIME=YesPlease if clock_gettime is available. GIT_CHECK_FUNC(clock_gettime, [HAVE_CLOCK_GETTIME=YesPlease], diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3918c87e3..34024754d9 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1339,15 +1339,15 @@ _git_help () { case "$cur" in --*) - __gitcomp "--all --info --man --web" + __gitcomp "--all --guides --info --man --web" return ;; esac __git_compute_all_commands __gitcomp "$__git_all_commands $(__git_aliases) attributes cli core-tutorial cvs-migration - diffcore gitk glossary hooks ignore modules - namespaces repository-layout tutorial tutorial-2 + diffcore everyday gitk glossary hooks ignore modules + namespaces repository-layout revisions tutorial tutorial-2 workflows " } @@ -1458,6 +1458,7 @@ _git_log () --relative-date --date= --pretty= --format= --oneline --show-signature + --cherry-mark --cherry-pick --graph --decorate --decorate= diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES index bc77e66b85..53c71b422a 100644 --- a/contrib/hooks/multimail/CHANGES +++ b/contrib/hooks/multimail/CHANGES @@ -1,3 +1,38 @@ +Release 1.3.0 +============= + +* New options multimailhook.htmlInIntro and multimailhook.htmlInFooter + now allow using HTML in the introduction and footer of emails (e.g. + for a more pleasant formatting or to insert a link to the commit on + a web interface). + +* A new option multimailhook.commitBrowseURL gives a simpler (and less + flexible) way to add a link to a web interface for commit emails + than multimailhook.htmlInIntro and multimailhook.htmlInFooter. + +* A new public function config.add_config_parameters was added to + allow custom hooks to set specific Git configuration variables + without modifying the configuration files. See an example in + post-receive.example. + +* Error handling for SMTP has been improved (we used to print Python + backtraces for legitimate errors). + +* The SMTP mailer can now check TLS certificates when the newly added + configuration variable multimailhook.smtpCACerts. + +* Python 3 portability has been improved. + +* The documentation's formatting has been improved. + +* The testsuite has been improved (we now use pyflakes to check for + errors in the code). + +This version has been tested with Python 2.4 and 2.6 to 3.5, and Git +v1.7.10-406-gdc801e7, 2.1.4 and 2.8.1.339.g3ad15fd. + +No change since 1.3 RC1. + Release 1.2.0 ============= diff --git a/contrib/hooks/multimail/CONTRIBUTING.rst b/contrib/hooks/multimail/CONTRIBUTING.rst index 09efdb059c..530ecbfcf1 100644 --- a/contrib/hooks/multimail/CONTRIBUTING.rst +++ b/contrib/hooks/multimail/CONTRIBUTING.rst @@ -1,3 +1,6 @@ +Contributing +============ + git-multimail is an open-source project, built by volunteers. We would welcome your help! @@ -6,9 +9,7 @@ and Matthieu Moy . Please note that although a copy of git-multimail is distributed in the "contrib" section of the main Git project, development takes place -in a separate git-multimail repository on GitHub: - - https://github.com/git-multimail/git-multimail +in a separate `git-multimail repository on GitHub`_. Whenever enough changes to git-multimail have accumulated, a new code-drop of git-multimail will be submitted for inclusion in the Git @@ -21,10 +22,12 @@ to the maintainers). Please sign off your patches as per the `Git project practice `__. -General discussion of git-multimail can take place on the main Git -mailing list, - - git@vger.kernel.org +General discussion of git-multimail can take place on the main `Git +mailing list`_. Please CC emails regarding git-multimail to the maintainers so that we don't overlook them. + + +.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail +.. _`Git mailing list`: git@vger.kernel.org diff --git a/contrib/hooks/multimail/README b/contrib/hooks/multimail/README index 55120685f0..1e04801978 100644 --- a/contrib/hooks/multimail/README +++ b/contrib/hooks/multimail/README @@ -1,5 +1,5 @@ -git-multimail (version 1.2.0) -============================= +git-multimail 1.3.0 +=================== .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master :target: https://travis-ci.org/git-multimail/git-multimail @@ -127,6 +127,13 @@ changes of this type, please consider sharing them with the community.) +Troubleshooting/FAQ +------------------- + +Please read ``__ for frequently asked +questions and common issues with git-multimail. + + Configuration ------------- @@ -134,19 +141,16 @@ By default, git-multimail mostly takes its configuration from the following ``git config`` settings: multimailhook.environment - This describes the general environment of the repository. In most cases, you do not need to specify a value for this variable: `git-multimail` will autodetect which environment to use. Currently supported values: - * generic - + generic the username of the pusher is read from $USER or $USERNAME and the repository name is derived from the repository's path. - * gitolite - + gitolite the username of the pusher is read from $GL_USER, the repository name is read from $GL_REPO, and the From: header value is optionally read from gitolite.conf (see multimailhook.from). @@ -154,8 +158,7 @@ multimailhook.environment For more information about gitolite and git-multimail, read ``__ - * stash - + stash Environment to use when ``git-multimail`` is ran as an Atlassian BitBucket Server (formerly known as Atlassian Stash) hook. @@ -169,8 +172,7 @@ multimailhook.environment and repo come from these two command line flags, which must be specified. - * gerrit - + gerrit Environment to use when ``git-multimail`` is ran as a ``ref-updated`` Gerrit hook. @@ -205,14 +207,12 @@ multimailhook.environment * If none of the above apply, then ``generic`` is used. multimailhook.repoName - A short name of this Git repository, to be used in various places in the notification email text. The default is to use $GL_REPO for gitolite repositories, or otherwise to derive this value from the repository path name. multimailhook.mailingList - The list of email addresses to which notification emails should be sent, as RFC 2822 email addresses separated by commas. This configuration option can be multivalued. Leave it unset or set it @@ -221,7 +221,6 @@ multimailhook.mailingList specific types of notification email. multimailhook.refchangeList - The list of email addresses to which summary emails about reference changes should be sent, as RFC 2822 email addresses separated by commas. This configuration option can be @@ -231,7 +230,6 @@ multimailhook.refchangeList multimailhook.mailingList is set. multimailhook.announceList - The list of email addresses to which emails about new annotated tags should be sent, as RFC 2822 email addresses separated by commas. This configuration option can be multivalued. The @@ -241,7 +239,6 @@ multimailhook.announceList even if one of the other values is set. multimailhook.commitList - The list of email addresses to which emails about individual new commits should be sent, as RFC 2822 email addresses separated by commas. This configuration option can be multivalued. The @@ -251,7 +248,6 @@ multimailhook.commitList multimailhook.mailingList is set. multimailhook.announceShortlog - If this option is set to true, then emails about changes to annotated tags include a shortlog of changes since the previous tag. This can be useful if the annotated tags represent releases; @@ -261,7 +257,6 @@ multimailhook.announceShortlog rather than useful. Default is false. multimailhook.commitEmailFormat - The format of email messages for the individual commits, can be "text" or "html". In the latter case, the emails will include diffs using colorized HTML instead of plain text used by default. Note that this currently the @@ -274,8 +269,43 @@ multimailhook.commitEmailFormat the message starting with ``+++`` or ``---`` colored in red or green). -multimailhook.refchangeShowGraph + By default, all the message is HTML-escaped. See + ``multimailhook.htmlInIntro`` to change this behavior. + +multimailhook.commitBrowseURL + Used to generate a link to an online repository browser in commit + emails. This variable must be a string. Format directives like + ``%()s`` will be expanded the same way as template + strings. In particular, ``%(id)s`` will be replaced by the full + Git commit identifier (40-chars hexadecimal). + + If the string does not contain any format directive, then + ``%(id)s`` will be automatically added to the string. If you don't + want ``%(id)s`` to be automatically added, use the empty format + directive ``%()s`` anywhere in the string. + + For example, a suitable value for the git-multimail project itself + would be + ``https://github.com/git-multimail/git-multimail/commit/%(id)s``. + +multimailhook.htmlInIntro, multimailhook.htmlInFooter + When generating an HTML message, git-multimail escapes any HTML + sequence by default. This means that if a template contains HTML + like ``link``, the reader will see the HTML + source code and not a proper link. + + Set ``multimailhook.htmlInIntro`` to true to allow writting HTML + formatting in introduction templates. Similarly, set + ``multimailhook.htmlInFooter`` for HTML in the footer. + Variables expanded in the template are still escaped. For example, + if a repository's path contains a ``<``, it will be rendered as + such in the message. + + Read ``__ for more details and + examples. + +multimailhook.refchangeShowGraph If this option is set to true, then summary emails about reference changes will additionally include: @@ -287,7 +317,6 @@ multimailhook.refchangeShowGraph specified in graphOpts. The default is false. multimailhook.refchangeShowLog - If this option is set to true, then summary emails about reference changes will include a detailed log of the added commits in addition to the one line summary. The log is generated by running @@ -295,71 +324,80 @@ multimailhook.refchangeShowLog Default is false. multimailhook.mailer - This option changes the way emails are sent. Accepted values are: - - sendmail (the default): use the command ``/usr/sbin/sendmail`` or + * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or ``/usr/lib/sendmail`` (or sendmailCommand, if configured). This mode can be further customized via the following options: - * multimailhook.sendmailCommand - - The command used by mailer ``sendmail`` to send emails. Shell - quoting is allowed in the value of this setting, but remember that - Git requires double-quotes to be escaped; e.g.:: + multimailhook.sendmailCommand + The command used by mailer ``sendmail`` to send emails. Shell + quoting is allowed in the value of this setting, but remember that + Git requires double-quotes to be escaped; e.g.:: - git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"' + git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"' - Default is '/usr/sbin/sendmail -oi -t' or - '/usr/lib/sendmail -oi -t' (depending on which file is - present and executable). + Default is '/usr/sbin/sendmail -oi -t' or + '/usr/lib/sendmail -oi -t' (depending on which file is + present and executable). - * multimailhook.envelopeSender + multimailhook.envelopeSender + If set then pass this value to sendmail via the -f option to set + the envelope sender address. - If set then pass this value to sendmail via the -f option to set - the envelope sender address. - - - smtp: use Python's smtplib. This is useful when the sendmail + * **smtp**: use Python's smtplib. This is useful when the sendmail command is not available on the system. This mode can be further customized via the following options: - * multimailhook.smtpServer - - The name of the SMTP server to connect to. The value can - also include a colon and a port number; e.g., - ``mail.example.com:25``. Default is 'localhost' using port 25. - - * multimailhook.smtpUser - * multimailhook.smtpPass - - Server username and password. Required if smtpEncryption is 'ssl'. - Note that the username and password currently need to be - set cleartext in the configuration file, which is not - recommended. If you need to use this option, be sure your - configuration file is read-only. + multimailhook.smtpServer + The name of the SMTP server to connect to. The value can + also include a colon and a port number; e.g., + ``mail.example.com:25``. Default is 'localhost' using port 25. - * multimailhook.envelopeSender + multimailhook.smtpUser, multimailhook.smtpPass + Server username and password. Required if smtpEncryption is 'ssl'. + Note that the username and password currently need to be + set cleartext in the configuration file, which is not + recommended. If you need to use this option, be sure your + configuration file is read-only. + multimailhook.envelopeSender The sender address to be passed to the SMTP server. If unset, then the value of multimailhook.from is used. - * multimailhook.smtpServerTimeout - + multimailhook.smtpServerTimeout Timeout in seconds. - * multimailhook.smtpEncryption - - Set the security type. Allowed values: none, ssl, tls. - Default=none. - - * multimailhook.smtpServerDebugLevel - + multimailhook.smtpEncryption + Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls). + Default is ``none``. + + multimailhook.smtpCACerts + Set the path to a list of trusted CA certificate to verify the + server certificate, only supported when ``smtpEncryption`` is + ``tls``. If unset or empty, the server certificate is not + verified. If it targets a file containing a list of trusted CA + certificates (PEM format) these CAs will be used to verify the + server certificate. For debian, you can set + ``/etc/ssl/certs/ca-certificates.crt`` for using the system + trusted CAs. For self-signed server, you can add your server + certificate to the system store:: + + cd /usr/local/share/ca-certificates/ + openssl s_client -starttls smtp \ + -connect mail.example.net:587 -showcerts \ + /dev/null \ + | openssl x509 -outform PEM >mail.example.net.crt + update-ca-certificates + + and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or + directly use your ``/path/to/mail.example.net.crt``. Default is + unset. + + multimailhook.smtpServerDebugLevel Integer number. Set to greater than 0 to activate debugging. -multimailhook.from -multimailhook.fromCommit -multimailhook.fromRefchange - +multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange If set, use this value in the From: field of generated emails. ``fromCommit`` is used for commit emails, ``fromRefchange`` is used for refchange emails, and ``from`` is used as fall-back in @@ -372,7 +410,7 @@ multimailhook.fromRefchange - The value ``pusher``, in which case the pusher's address (if available) will be used. - - The value ``author`` (meaningful only for replyToCommit), in which + - The value ``author`` (meaningful only for ``fromCommit``), in which case the commit author's address will be used. If config values are unset, the value of the From: header is @@ -396,14 +434,12 @@ multimailhook.fromRefchange 3. Use the value of multimailhook.envelopeSender. multimailhook.administrator - The name and/or email address of the administrator of the Git repository; used in FOOTER_TEMPLATE. Default is multimailhook.envelopesender if it is set; otherwise a generic string is used. multimailhook.emailPrefix - All emails have this string prepended to their subjects, to aid email filtering (though filtering based on the X-Git-* email headers is probably more robust). Default is the short name of @@ -411,16 +447,14 @@ multimailhook.emailPrefix value to the empty string to suppress the email prefix. multimailhook.emailMaxLines - The maximum number of lines that should be included in the body of a generated email. If not specified, there is no limit. Lines beyond the limit are suppressed and counted, and a final line is added indicating the number of suppressed lines. multimailhook.emailMaxLineLength - The maximum length of a line in the email body. Lines longer than - this limit are truncated to this length with a trailing `` [...]`` + this limit are truncated to this length with a trailing ``[...]`` added to indicate the missing text. The default is 500, because (a) diffs with longer lines are probably from binary files, for which a diff is useless, and (b) even if a text file has such long @@ -428,7 +462,6 @@ multimailhook.emailMaxLineLength truncation, set this option to 0. multimailhook.maxCommitEmails - The maximum number of commit emails to send for a given change. When the number of patches is larger that this value, only the summary refchange email is sent. This can avoid accidental @@ -436,14 +469,12 @@ multimailhook.maxCommitEmails emails limit, set this option to 0. The default is 500. multimailhook.emailStrictUTF8 - If this boolean option is set to `true`, then the main part of the email body is forced to be valid UTF-8. Any characters that are not valid UTF-8 are converted to the Unicode replacement character, U+FFFD. The default is `true`. multimailhook.diffOpts - Options passed to ``git diff-tree`` when generating the summary information for ReferenceChange emails. Default is ``--stat --summary --find-copies-harder``. Add -p to those options to @@ -452,7 +483,6 @@ multimailhook.diffOpts details. multimailhook.graphOpts - Options passed to ``git log --graph`` when generating graphs for the reference change summary emails (used only if refchangeShowGraph is true). The default is '--oneline --decorate'. @@ -460,7 +490,6 @@ multimailhook.graphOpts Shell quoting is allowed; see logOpts for details. multimailhook.logOpts - Options passed to ``git log`` to generate additional info for reference change emails (used only if refchangeShowLog is set). For example, adding -p will show each commit's complete diff. The @@ -479,7 +508,6 @@ multimailhook.logOpts logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\" multimailhook.commitLogOpts - Options passed to ``git log`` to generate additional info for revision change emails. For example, adding --ignore-all-spaces will suppress whitespace changes. The default options are ``-C @@ -487,26 +515,21 @@ multimailhook.commitLogOpts multimailhook.logOpts for details. multimailhook.dateSubstitute - String to use as a substitute for ``Date:`` in the output of ``git log`` while formatting commit messages. This is usefull to avoid emitting a line that can be interpreted by mailers as the start of a cited message (Zimbra webmail in particular). Defaults to - ``CommitDate: ``. Set to an empty string or ``none`` to deactivate + ``CommitDate:``. Set to an empty string or ``none`` to deactivate the behavior. multimailhook.emailDomain - Domain name appended to the username of the person doing the push to convert it into an email address (via ``"%s@%s" % (username, emaildomain)``). More complicated schemes can be implemented by overriding Environment and overriding its get_pusher_email() method. -multimailhook.replyTo -multimailhook.replyToCommit -multimailhook.replyToRefchange - +multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange Addresses to use in the Reply-To: field for commit emails (replyToCommit) and refchange emails (replyToRefchange). multimailhook.replyTo is used as default when replyToCommit or @@ -519,32 +542,24 @@ multimailhook.replyToRefchange commit emails. multimailhook.quiet - Do not output the list of email recipients from the hook multimailhook.stdout - For debugging, send emails to stdout rather than to the mailer. Equivalent to the --stdout command line option multimailhook.scanCommitForCc - If this option is set to true, than recipients from lines in commit body that starts with ``CC:`` will be added to CC list. Default: false multimailhook.combineWhenSingleCommit - If this option is set to true and a single new commit is pushed to a branch, combine the summary and commit email messages into a single email. Default: true -multimailhook.refFilterInclusionRegex -multimailhook.refFilterExclusionRegex -multimailhook.refFilterDoSendRegex -multimailhook.refFilterDontSendRegex - +multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex **Warning:** these options are experimental. They should work, but the user-interface is not stable yet (in particular, the option names may change). If you want to participate in stabilizing the @@ -626,14 +641,16 @@ git-multimail is mostly customized via an "environment" that describes the local environment in which Git is running. Two types of environment are built in: -* GenericEnvironment: a stand-alone Git repository. +GenericEnvironment + a stand-alone Git repository. -* GitoliteEnvironment: a Git repository that is managed by gitolite - [3]_. For such repositories, the identity of the pusher is read from - environment variable $GL_USER, the name of the repository is read - from $GL_REPO (if it is not overridden by multimailhook.reponame), - and the From: header value is optionally read from gitolite.conf - (see multimailhook.from). +GitoliteEnvironment + a Git repository that is managed by gitolite + [3]_. For such repositories, the identity of the pusher is read from + environment variable $GL_USER, the name of the repository is read + from $GL_REPO (if it is not overridden by multimailhook.reponame), + and the From: header value is optionally read from gitolite.conf + (see multimailhook.from). By default, git-multimail assumes GitoliteEnvironment if $GL_USER and $GL_REPO are set, and otherwise assumes GenericEnvironment. diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git index 300a2a4d2d..ee1fa75f99 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 October 11 2015 and consists of the "git-multimail" subdirectory from +on May 03 2016 and consists of the "git-multimail" subdirectory from revision - c0791b9ef5821a746fc3475c25765e640452eaae refs/tags/1.2.0 + 26f3ae9f86aa7f8a054ba89235c4d3879f98b03d refs/tags/1.3.0 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/doc/customizing-emails.rst b/contrib/hooks/multimail/doc/customizing-emails.rst new file mode 100644 index 0000000000..3f5b67f768 --- /dev/null +++ b/contrib/hooks/multimail/doc/customizing-emails.rst @@ -0,0 +1,56 @@ +Customizing the content and formatting of emails +================================================ + +Overloading template strings +---------------------------- + +The content of emails is generated based on template strings defined +in ``git_multimail.py``. You can customize these template strings +without changing the script itself, by defining a Python wrapper +around it. The python wrapper should ``import git_multimail`` and then +override the ``git_multimail.*`` strings like this:: + + import sys # needed for sys.argv + + # Import and customize git_multimail: + import git_multimail + git_multimail.REVISION_INTRO_TEMPLATE = """...""" + git_multimail.COMBINED_INTRO_TEMPLATE = git_multimail.REVISION_INTRO_TEMPLATE + + # start git_multimail itself: + git_multimail.main(sys.argv[1:]) + +The template strings can use any value already used in the existing +templates (read the source code). + +Using HTML in template strings +------------------------------ + +If ``multimailhook.commitEmailFormat`` is set to HTML, then +git-multimail will generate HTML emails for commit notifications. The +log and diff will be formatted automatically by git-multimail. By +default, any HTML special character in the templates will be escaped. + +To use HTML formatting in the introduction of the email, set +``multimailhook.htmlInIntro`` to ``true``. Then, the template can +contain any HTML tags, that will be sent as-is in the email. For +example, to add some formatting and a link to the online commit, use +a format like:: + + git_multimail.REVISION_INTRO_TEMPLATE = """\ + This is an automated email from the git hooks/post-receive script.

+ + %(pusher)s pushed a commit to %(refname_type)s %(short_refname)s + in repository %(repo_shortname)s.
+ + View on GitHub. + """ + +Note that the values expanded from ``%(variable)s`` in the format +strings will still be escaped. + +For a less flexible but easier to set up way to add a link to commit +emails, see ``multimailhook.commitBrowseURL``. + +Similarly, one can set ``multimailhook.htmlInFooter`` and override any +of the ``*_FOOTER*`` template strings. diff --git a/contrib/hooks/multimail/doc/troubleshooting.rst b/contrib/hooks/multimail/doc/troubleshooting.rst new file mode 100644 index 0000000000..d3f346f076 --- /dev/null +++ b/contrib/hooks/multimail/doc/troubleshooting.rst @@ -0,0 +1,44 @@ +Troubleshooting issues with git-multimail: a FAQ +================================================ + +Git is not using the right address in the From/To/Reply-To field +---------------------------------------------------------------- + +First, make sure that git-multimail actually uses what you think it is +using. A lot happens to your email (especially when posting to a +mailing-list) between the time `git_multimail.py` sends it and the +time it reaches your inbox. + +A simple test (to do on a test repository, do not use in production as +it would disable email sending): change your post-receive hook to call +`git_multimail.py` with the `--stdout` option, and try to push to the +repository. You should see something like:: + + Counting objects: 3, done. + Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done. + Total 3 (delta 0), reused 0 (delta 0) + remote: Sending notification emails to: foo.bar@example.com + remote: =========================================================================== + remote: Date: Mon, 25 Apr 2016 18:39:59 +0200 + remote: To: foo.bar@example.com + remote: Subject: [git] branch master updated: foo + remote: MIME-Version: 1.0 + remote: Content-Type: text/plain; charset=utf-8 + remote: Content-Transfer-Encoding: 8bit + remote: Message-ID: <20160425163959.2311.20498@anie> + remote: From: Auth Or + remote: Reply-To: Auth Or + remote: X-Git-Host: example + ... + remote: -- + remote: To stop receiving notification emails like this one, please contact + remote: the administrator of this repository. + remote: =========================================================================== + To /path/to/repo + 6278f04..e173f20 master -> master + +Note: this does not include the sender (Return-Path: header), as it is +not part of the message content but passed to the mailer. Some mailer +show the ``Sender:`` field instead of the ``From:`` field (for +example, Zimbra Webmail shows ``From: on behalf of +``). diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py index 0180dba431..f2c92aeed8 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.2.0' +__version__ = '1.3.0' # Copyright (c) 2015 Matthieu Moy and others # Copyright (c) 2012-2014 Michael Haggerty and others @@ -57,6 +57,11 @@ import shlex import optparse import smtplib +try: + import ssl +except ImportError: + # Python < 2.6 do not have ssl, but that's OK if we don't use it. + pass import time import cgi @@ -75,6 +80,9 @@ def is_ascii(s): if PYTHON3: + def is_string(s): + return isinstance(s, str) + def str_to_bytes(s): return s.encode(ENCODING) @@ -91,6 +99,12 @@ def write_str(f, msg): except UnicodeEncodeError: f.buffer.write(msg.encode(ENCODING)) else: + def is_string(s): + try: + return isinstance(s, basestring) + except NameError: # Silence Pyflakes warning + raise + def str_to_bytes(s): return s @@ -313,6 +327,16 @@ def next(it): """ +LINK_TEXT_TEMPLATE = """\ +View the commit online: +%(browse_url)s + +""" + +LINK_HTML_TEMPLATE = """\ +

View the commit online.

+""" + REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE @@ -532,6 +556,28 @@ def _split(s): assert words[-1] == '' return words[:-1] + @staticmethod + def add_config_parameters(c): + """Add configuration parameters to Git. + + c is either an str or a list of str, each element being of the + form 'var=val' or 'var', with the same syntax and meaning as + the argument of 'git -c var=val'. + """ + if isinstance(c, str): + c = (c,) + parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '') + if parameters: + parameters += ' ' + # git expects GIT_CONFIG_PARAMETERS to be of the form + # "'name1=value1' 'name2=value2' 'name3=value3'" + # including everything inside the double quotes (but not the double + # quotes themselves). Spacing is critical. Also, if a value contains + # a literal single quote that quote must be represented using the + # four character sequence: '\'' + parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c) + os.environ['GIT_CONFIG_PARAMETERS'] = parameters + def get(self, name, default=None): try: values = self._split(read_git_output( @@ -745,6 +791,12 @@ def _compute_values(self): values['multimail_version'] = get_version() return values + # Aliases usable in template strings. Tuple of pairs (destination, + # source). + VALUES_ALIAS = ( + ("id", "newrev"), + ) + def get_values(self, **extra_values): """Return a dictionary {keyword: expansion} for this Change. @@ -760,6 +812,9 @@ def get_values(self, **extra_values): values = self._values.copy() if extra_values: values.update(extra_values) + + for alias, val in self.VALUES_ALIAS: + values[alias] = values[val] return values def expand(self, template, **extra_values): @@ -772,10 +827,14 @@ def expand(self, template, **extra_values): return template % self.get_values(**extra_values) - def expand_lines(self, template, **extra_values): + def expand_lines(self, template, html_escape_val=False, **extra_values): """Break template into lines and expand each line.""" values = self.get_values(**extra_values) + if html_escape_val: + for k in values: + if is_string(values[k]): + values[k] = cgi.escape(values[k], True) for line in template.splitlines(True): yield line % values @@ -787,9 +846,10 @@ def expand_header_lines(self, template, **extra_values): values = self.get_values(**extra_values) if self._contains_html_diff: - values['contenttype'] = 'html' + self._content_type = 'html' else: - values['contenttype'] = 'plain' + self._content_type = 'plain' + values['contenttype'] = self._content_type for line in template.splitlines(): (name, value) = line.split(': ', 1) @@ -819,7 +879,11 @@ def generate_email_header(self): raise NotImplementedError() - def generate_email_intro(self): + def generate_browse_link(self, base_url): + """Generate a link to an online repository browser.""" + return iter(()) + + def generate_email_intro(self, html_escape_val=False): """Generate the email intro for this Change, a line at a time. The output will be used as the standard boilerplate at the top @@ -835,7 +899,7 @@ def generate_email_body(self): raise NotImplementedError() - def generate_email_footer(self): + def generate_email_footer(self, html_escape_val): """Generate the footer of the email, a line at a time. The footer is always included, irrespective of @@ -876,9 +940,18 @@ def generate_email(self, push, body_filter=None, extra_header_values={}): for line in self.generate_email_header(**extra_header_values): yield line yield '\n' - for line in self._wrap_for_html(self.generate_email_intro()): + html_escape_val = (self.environment.html_in_intro and + self._contains_html_diff) + intro = self.generate_email_intro(html_escape_val) + if not self.environment.html_in_intro: + intro = self._wrap_for_html(intro) + for line in intro: yield line + if self.environment.commitBrowseURL: + for line in self.generate_browse_link(self.environment.commitBrowseURL): + yield line + body = self.generate_email_body(push) if body_filter is not None: body = body_filter(body) @@ -939,8 +1012,12 @@ def generate_email(self, push, body_filter=None, extra_header_values={}): yield line if self._contains_html_diff: yield '' - - for line in self._wrap_for_html(self.generate_email_footer()): + html_escape_val = (self.environment.html_in_footer and + self._contains_html_diff) + footer = self.generate_email_footer(html_escape_val) + if not self.environment.html_in_footer: + footer = self._wrap_for_html(footer) + for line in footer: yield line def get_alt_fromaddr(self): @@ -992,6 +1069,7 @@ def _compute_values(self): values['rev_short'] = self.rev.short values['change_type'] = self.change_type values['refname'] = self.refname + values['newrev'] = self.rev.sha1 values['short_refname'] = self.reference_change.short_refname values['refname_type'] = self.reference_change.refname_type values['reply_to_msgid'] = self.reference_change.msgid @@ -1015,8 +1093,26 @@ def generate_email_header(self, **extra_values): ): yield line - def generate_email_intro(self): - for line in self.expand_lines(REVISION_INTRO_TEMPLATE): + def generate_browse_link(self, base_url): + if '%(' not in base_url: + base_url += '%(id)s' + url = "".join(self.expand_lines(base_url)) + if self._content_type == 'html': + for line in self.expand_lines(LINK_HTML_TEMPLATE, + html_escape_val=True, + browse_url=url): + yield line + elif self._content_type == 'plain': + for line in self.expand_lines(LINK_TEXT_TEMPLATE, + html_escape_val=False, + browse_url=url): + yield line + else: + raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.") + + def generate_email_intro(self, html_escape_val=False): + for line in self.expand_lines(REVISION_INTRO_TEMPLATE, + html_escape_val=html_escape_val): yield line def generate_email_body(self, push): @@ -1031,8 +1127,9 @@ def generate_email_body(self, push): else: yield line - def generate_email_footer(self): - return self.expand_lines(REVISION_FOOTER_TEMPLATE) + def generate_email_footer(self, html_escape_val): + return self.expand_lines(REVISION_FOOTER_TEMPLATE, + html_escape_val=html_escape_val) def generate_email(self, push, body_filter=None, extra_header_values={}): self._contains_diff() @@ -1217,8 +1314,9 @@ def generate_email_header(self, **extra_values): ): yield line - def generate_email_intro(self): - for line in self.expand_lines(self.intro_template): + def generate_email_intro(self, html_escape_val=False): + for line in self.expand_lines(self.intro_template, + html_escape_val=html_escape_val): yield line def generate_email_body(self, push): @@ -1238,8 +1336,9 @@ def generate_email_body(self, push): for line in self.generate_revision_change_summary(push): yield line - def generate_email_footer(self): - return self.expand_lines(self.footer_template) + def generate_email_footer(self, html_escape_val): + return self.expand_lines(self.footer_template, + html_escape_val=html_escape_val) def generate_revision_change_graph(self, push): if self.showgraph: @@ -1896,6 +1995,7 @@ def __init__(self, envelopesender, smtpserver, smtpservertimeout=10.0, smtpserverdebuglevel=0, smtpencryption='none', smtpuser='', smtppass='', + smtpcacerts='' ): if not envelopesender: sys.stderr.write( @@ -1915,6 +2015,7 @@ def __init__(self, envelopesender, smtpserver, self.security = smtpencryption self.username = smtpuser self.password = smtppass + self.smtpcacerts = smtpcacerts try: def call(klass, server, timeout): try: @@ -1925,13 +2026,56 @@ def call(klass, server, timeout): if self.security == 'none': self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout) elif self.security == 'ssl': + if self.smtpcacerts: + raise smtplib.SMTPException( + "Checking certificate is not supported for ssl, prefer starttls" + ) self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout) elif self.security == 'tls': + if 'ssl' not in sys.modules: + sys.stderr.write( + '*** Your Python version does not have the ssl library installed\n' + '*** smtpEncryption=tls is not available.\n' + '*** Either upgrade Python to 2.6 or later\n' + ' or use git_multimail.py version 1.2.\n') if ':' not in self.smtpserver: self.smtpserver += ':587' # default port for TLS self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout) + # start: ehlo + starttls + # equivalent to + # self.smtp.ehlo() + # self.smtp.starttls() + # with acces to the ssl layer self.smtp.ehlo() - self.smtp.starttls() + if not self.smtp.has_extn("starttls"): + raise smtplib.SMTPException("STARTTLS extension not supported by server") + resp, reply = self.smtp.docmd("STARTTLS") + if resp != 220: + raise smtplib.SMTPException("Wrong answer to the STARTTLS command") + if self.smtpcacerts: + self.smtp.sock = ssl.wrap_socket( + self.smtp.sock, + ca_certs=self.smtpcacerts, + cert_reqs=ssl.CERT_REQUIRED + ) + else: + self.smtp.sock = ssl.wrap_socket( + self.smtp.sock, + cert_reqs=ssl.CERT_NONE + ) + sys.stderr.write( + '*** Warning, the server certificat is not verified (smtp) ***\n' + '*** set the option smtpCACerts ***\n' + ) + if not hasattr(self.smtp.sock, "read"): + # using httplib.FakeSocket with Python 2.5.x or earlier + self.smtp.sock.read = self.smtp.sock.recv + self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock) + self.smtp.helo_resp = None + self.smtp.ehlo_resp = None + self.smtp.esmtp_features = {} + self.smtp.does_esmtp = 0 + # end: ehlo + starttls self.smtp.ehlo() else: sys.stdout.write('*** Error: Control reached an invalid option. ***') @@ -1951,6 +2095,7 @@ def call(klass, server, timeout): def __del__(self): if hasattr(self, 'smtp'): self.smtp.quit() + del self.smtp def send(self, lines, to_addrs): try: @@ -1958,13 +2103,24 @@ def send(self, lines, to_addrs): self.smtp.login(self.username, self.password) msg = ''.join(lines) # turn comma-separated list into Python list if needed. - if isinstance(to_addrs, basestring): + if is_string(to_addrs): to_addrs = [email for (name, email) in getaddresses([to_addrs])] self.smtp.sendmail(self.envelopesender, to_addrs, msg) - except Exception: + except smtplib.SMTPResponseException: sys.stderr.write('*** Error sending email ***\n') - sys.stderr.write('*** %s\n' % sys.exc_info()[1]) - self.smtp.quit() + err = sys.exc_info()[1] + sys.stderr.write('*** Error %d: %s\n' % (err.smtp_code, + bytes_to_str(err.smtp_error))) + try: + smtp = self.smtp + # delete the field before quit() so that in case of + # error, self.smtp is deleted anyway. + del self.smtp + smtp.quit() + except: + sys.stderr.write('*** Error closing the SMTP connection ***\n') + sys.stderr.write('*** Exiting anyway ... ***\n') + sys.stderr.write('*** %s\n' % sys.exc_info()[1]) sys.exit(1) @@ -2097,6 +2253,14 @@ class Environment(object): If "html", generate commit emails in HTML instead of plain text used by default. + html_in_intro (bool) + html_in_footer (bool) + + When generating HTML emails, the introduction (respectively, + the footer) will be HTML-escaped iff html_in_intro (respectively, + the footer) is true. When false, only the values used to expand + the template are escaped. + refchange_showgraph (bool) True iff refchanges emails should include a detailed graph. @@ -2160,6 +2324,9 @@ def __init__(self, osenv=None): self.osenv = osenv or os.environ self.announce_show_shortlog = False self.commit_email_format = "text" + self.html_in_intro = False + self.html_in_footer = False + self.commitBrowseURL = None self.maxcommitemails = 500 self.diffopts = ['--stat', '--summary', '--find-copies-harder'] self.graphopts = ['--oneline', '--decorate'] @@ -2236,7 +2403,7 @@ def get_values(self): The return value is always a new dictionary.""" if self._values is None: - values = {} + values = {'': ''} # %()s expands to the empty string. for key in self.COMPUTED_KEYS: value = getattr(self, 'get_%s' % (key,))() @@ -2375,6 +2542,16 @@ def __init__(self, config, **kw): else: self.commit_email_format = commit_email_format + html_in_intro = config.get_bool('htmlInIntro') + if html_in_intro is not None: + self.html_in_intro = html_in_intro + + html_in_footer = config.get_bool('htmlInFooter') + if html_in_footer is not None: + self.html_in_footer = html_in_footer + + self.commitBrowseURL = config.get('commitBrowseURL') + maxcommitemails = config.get('maxcommitemails') if maxcommitemails is not None: try: @@ -2415,7 +2592,6 @@ def __init__(self, config, **kw): ['author']) self.__reply_to_commit = config.get('replyToCommit', default=reply_to) - from_addr = self.config.get('from') self.from_refchange = config.get('fromRefchange') self.forbid_field_values('fromRefchange', self.from_refchange, @@ -3390,6 +3566,8 @@ def run_as_post_receive_hook(environment, mailer): if changes: push = Push(environment, changes) push.send_emails(mailer, body_filter=environment.filter_body) + if hasattr(mailer, '__del__'): + mailer.__del__() def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False): @@ -3406,6 +3584,8 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send= ] push = Push(environment, changes, force_send) push.send_emails(mailer, body_filter=environment.filter_body) + if hasattr(mailer, '__del__'): + mailer.__del__() def choose_mailer(config, environment): @@ -3418,6 +3598,7 @@ def choose_mailer(config, environment): smtpencryption = config.get('smtpencryption', default='none') smtpuser = config.get('smtpuser', default='') smtppass = config.get('smtppass', default='') + smtpcacerts = config.get('smtpcacerts', default='') mailer = SMTPMailer( envelopesender=(environment.get_sender() or environment.get_fromaddr()), smtpserver=smtpserver, smtpservertimeout=smtpservertimeout, @@ -3425,6 +3606,7 @@ def choose_mailer(config, environment): smtpencryption=smtpencryption, smtpuser=smtpuser, smtppass=smtppass, + smtpcacerts=smtpcacerts ) elif mailer == 'sendmail': command = config.get('sendmailcommand') @@ -3691,17 +3873,7 @@ def main(args): return if options.c: - parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '') - if parameters: - parameters += ' ' - # git expects GIT_CONFIG_PARAMETERS to be of the form - # "'name1=value1' 'name2=value2' 'name3=value3'" - # including everything inside the double quotes (but not the double - # quotes themselves). Spacing is critical. Also, if a value contains - # a literal single quote that quote must be represented using the - # four character sequence: '\'' - parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c) - os.environ['GIT_CONFIG_PARAMETERS'] = parameters + Config.add_config_parameters(options.c) config = Config('multimailhook') diff --git a/contrib/hooks/multimail/post-receive.example b/contrib/hooks/multimail/post-receive.example index 9975df7107..1ea113d274 100755 --- a/contrib/hooks/multimail/post-receive.example +++ b/contrib/hooks/multimail/post-receive.example @@ -55,6 +55,12 @@ import git_multimail # git-multimail: config = git_multimail.Config('multimailhook') +# Set some Git configuration variables. Equivalent to passing var=val +# to "git -c var=val" each time git is called, or to adding the +# configuration in .git/config (must come before instanciating the +# environment) : +#git_multimail.Config.add_config_parameters('multimailhook.commitEmailFormat=html') +#git_multimail.Config.add_config_parameters(('user.name=foo', 'user.email=foo@example.com')) # Select the type of environment: try: diff --git a/credential-cache.c b/credential-cache.c index f4afdc6988..86e21de49b 100644 --- a/credential-cache.c +++ b/credential-cache.c @@ -32,6 +32,7 @@ static int send_request(const char *socket, const struct strbuf *out) write_or_die(1, in, r); got_data = 1; } + close(fd); return got_data; } diff --git a/credential.c b/credential.c index 7d6501d190..aa996669fc 100644 --- a/credential.c +++ b/credential.c @@ -63,9 +63,12 @@ static int credential_config_callback(const char *var, const char *value, key = dot + 1; } - if (!strcmp(key, "helper")) - string_list_append(&c->helpers, value); - else if (!strcmp(key, "username")) { + if (!strcmp(key, "helper")) { + if (*value) + string_list_append(&c->helpers, value); + else + string_list_clear(&c->helpers, 0); + } else if (!strcmp(key, "username")) { if (!c->username) c->username = xstrdup(value); } diff --git a/diff.c b/diff.c index 059123c5dc..4dfe6609d0 100644 --- a/diff.c +++ b/diff.c @@ -168,6 +168,11 @@ long parse_algorithm_value(const char *value) * never be affected by the setting of diff.renames * the user happens to have in the configuration file. */ +void init_diff_ui_defaults(void) +{ + diff_detect_rename_default = 1; +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff --git a/diff.h b/diff.h index e7d68edaf9..125447be09 100644 --- a/diff.h +++ b/diff.h @@ -266,6 +266,7 @@ extern int parse_long_opt(const char *opt, const char **argv, const char **optarg); extern int git_diff_basic_config(const char *var, const char *value, void *cb); +extern void init_diff_ui_defaults(void); extern int git_diff_ui_config(const char *var, const char *value, void *cb); extern void diff_setup(struct diff_options *); extern int diff_opt_parse(struct diff_options *, const char **, int, const char *); diff --git a/diffcore-rename.c b/diffcore-rename.c index 3b3c1ed535..7f03eb5a04 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -340,9 +340,11 @@ static int find_exact_renames(struct diff_options *options) int i, renames = 0; struct hashmap file_table; - /* Add all sources to the hash table */ + /* Add all sources to the hash table in reverse order, because + * later on they will be retrieved in LIFO order. + */ hashmap_init(&file_table, NULL, rename_src_nr); - for (i = 0; i < rename_src_nr; i++) + for (i = rename_src_nr-1; i >= 0; i--) insert_file_table(&file_table, i, rename_src[i].p->one); /* Walk the destinations and find best source match */ diff --git a/dir.c b/dir.c index 996653b0d3..656f272adc 100644 --- a/dir.c +++ b/dir.c @@ -64,13 +64,6 @@ int strncmp_icase(const char *a, const char *b, size_t count) return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } -int fnmatch_icase(const char *pattern, const char *string, int flags) -{ - return wildmatch(pattern, string, - flags | (ignore_case ? WM_CASEFOLD : 0), - NULL); -} - int git_fnmatch(const struct pathspec_item *item, const char *pattern, const char *string, int prefix) diff --git a/dir.h b/dir.h index 301b737a37..d56d2fb48f 100644 --- a/dir.h +++ b/dir.h @@ -272,7 +272,6 @@ 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 fnmatch_icase(const char *pattern, const char *string, int flags); /* * The prefix part of pattern must not contains wildcards. diff --git a/environment.c b/environment.c index 6cc0a7780f..57acb2fe2a 100644 --- a/environment.c +++ b/environment.c @@ -25,11 +25,9 @@ int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int ref_paranoia = -1; -int repository_format_version; int repository_format_precious_objects; const char *git_commit_encoding; const char *git_log_output_encoding; -int shared_repository = PERM_UMASK; const char *apply_default_whitespace; const char *apply_default_ignorewhitespace; const char *git_attributes_file; @@ -324,3 +322,24 @@ const char *get_commit_output_encoding(void) { return git_commit_encoding ? git_commit_encoding : "UTF-8"; } + +static int the_shared_repository = PERM_UMASK; +static int need_shared_repository_from_config = 1; + +void set_shared_repository(int value) +{ + the_shared_repository = value; + need_shared_repository_from_config = 0; +} + +int get_shared_repository(void) +{ + if (need_shared_repository_from_config) { + const char *var = "core.sharedrepository"; + const char *value; + if (!git_config_get_value(var, &value)) + the_shared_repository = git_config_perm(var, value); + need_shared_repository_from_config = 0; + } + return the_shared_repository; +} diff --git a/fetch-pack.c b/fetch-pack.c index f96f6dfb35..b501d5c320 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -15,7 +15,6 @@ #include "version.h" #include "prio-queue.h" #include "sha1-array.h" -#include "sigchain.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -674,10 +673,8 @@ static int sideband_demux(int in, int out, void *data) int *xd = data; int ret; - sigchain_push(SIGPIPE, SIG_IGN); ret = recv_sideband("fetch-pack", xd[0], out); close(out); - sigchain_pop(SIGPIPE); return ret; } @@ -701,6 +698,7 @@ static int get_pack(struct fetch_pack_args *args, demux.proc = sideband_demux; demux.data = xd; demux.out = -1; + demux.isolate_sigpipe = 1; if (start_async(&demux)) die("fetch-pack: unable to fork off sideband" " demultiplexer"); diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 77876d433a..822f857038 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -45,6 +45,7 @@ my $normal_color = $repo->get_color("", "reset"); my $diff_algorithm = $repo->config('diff.algorithm'); +my $diff_filter = $repo->config('interactive.difffilter'); my $use_readkey = 0; my $use_termcap = 0; @@ -754,7 +755,14 @@ sub parse_diff { my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); + my @display_cmd = ("git", @diff_cmd, qw(--color --), $path); + if (defined $diff_filter) { + # quotemeta is overkill, but sufficient for shell-quoting + my $diff = join(' ', map { quotemeta } @display_cmd); + @display_cmd = ("$diff | $diff_filter"); + } + + @colored = run_cmd_pipe(@display_cmd); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -765,7 +773,7 @@ sub parse_diff { } push @{$hunk[-1]{TEXT}}, $diff[$i]; push @{$hunk[-1]{DISPLAY}}, - ($diff_use_color ? $colored[$i] : $diff[$i]); + (@colored ? $colored[$i] : $diff[$i]); } return @hunk; } diff --git a/git-compat-util.h b/git-compat-util.h index 474395471f..1f8b5f3b1f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -279,9 +279,6 @@ extern char *gitdirname(char *); #endif #include #include -#ifdef NO_HMAC_CTX_CLEANUP -#define HMAC_CTX_cleanup HMAC_cleanup -#endif #endif /* On most systems would have given us this, but diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 2b11b1d6fe..84d6cc021c 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -44,10 +44,10 @@ launch_merge_tool () { "$GIT_DIFF_PATH_TOTAL" "$MERGED" if use_ext_cmd then - printf "Launch '%s' [Y/n]: " \ + printf "Launch '%s' [Y/n]? " \ "$GIT_DIFFTOOL_EXTCMD" else - printf "Launch '%s' [Y/n]: " "$merge_tool" + printf "Launch '%s' [Y/n]? " "$merge_tool" fi read ans || return if test "$ans" = n diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 8643f74cb0..dc2fd1b5a4 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -44,6 +44,12 @@ esac # MRC is the current "merge reference commit" # MRT is the current "merge result tree" +if ! git diff-index --quiet --cached HEAD -- +then + echo "Error: Your local changes to the following files would be overwritten by merge" + git diff-index --cached --name-only HEAD -- | sed -e 's/^/ /' + exit 2 +fi MRC=$(git rev-parse --verify -q $head) MRT=$(git write-tree) NON_FF_MERGE=0 diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 54ac8e4846..9abd00be21 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -100,7 +100,7 @@ check_unchanged () { while true do echo "$MERGED seems unchanged." - printf "Was the merge successful? [y/n] " + printf "Was the merge successful [y/n]? " read answer || return 1 case "$answer" in y*|Y*) return 0 ;; @@ -372,3 +372,28 @@ get_merge_tool () { fi echo "$merge_tool" } + +mergetool_find_win32_cmd () { + executable=$1 + sub_directory=$2 + + # Use $executable if it exists in $PATH + if type -p "$executable" >/dev/null 2>&1 + then + printf '%s' "$executable" + return + fi + + # Look for executable in the typical locations + for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' | + cut -d '=' -f 2- | sort -u) + do + if test -n "$directory" && test -x "$directory/$sub_directory/$executable" + then + printf '%s' "$directory/$sub_directory/$executable" + return + fi + done + + printf '%s' "$executable" +} diff --git a/git-mergetool.sh b/git-mergetool.sh index f67bab55e8..bf862705d8 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -413,7 +413,7 @@ done prompt_after_failed_merge () { while true do - printf "Continue merging other unresolved paths (y/n) ? " + printf "Continue merging other unresolved paths [y/n]? " read ans || return 1 case "$ans" in [yY]*) diff --git a/git-p4.py b/git-p4.py index 825b9f32d5..8f869d74a1 100755 --- a/git-p4.py +++ b/git-p4.py @@ -1160,6 +1160,15 @@ def getUserMapFromPerforceServer(self): self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" self.emails[output["Email"]] = output["User"] + mapUserConfigRegex = re.compile(r"^\s*(\S+)\s*=\s*(.+)\s*<(\S+)>\s*$", re.VERBOSE) + for mapUserConfig in gitConfigList("git-p4.mapUser"): + mapUser = mapUserConfigRegex.findall(mapUserConfig) + if mapUser and len(mapUser[0]) == 3: + user = mapUser[0][0] + fullname = mapUser[0][1] + email = mapUser[0][2] + self.users[user] = fullname + " <" + email + ">" + self.emails[email] = user s = '' for (key, val) in self.users.items(): @@ -2311,6 +2320,15 @@ def extractFilesFromCommit(self, commit): fnum = fnum + 1 return files + def extractJobsFromCommit(self, commit): + jobs = [] + jnum = 0 + while commit.has_key("job%s" % jnum): + job = commit["job%s" % jnum] + jobs.append(job) + jnum = jnum + 1 + return jobs + def stripRepoPath(self, path, prefixes): """When streaming files, this is called to map a p4 depot path to where it should go in git. The prefixes are either @@ -2656,6 +2674,7 @@ def hasBranchPrefix(self, path): def commit(self, details, files, branch, parent = ""): epoch = details["time"] author = details["user"] + jobs = self.extractJobsFromCommit(details) if self.verbose: print('commit into {0}'.format(branch)) @@ -2683,6 +2702,8 @@ def commit(self, details, files, branch, parent = ""): self.gitStream.write("data < 0: + self.gitStream.write("\nJobs: %s" % (' '.join(jobs))) self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s" % (','.join(self.branchPrefixes), details["change"])) if len(details['options']) > 0: diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 4cde685b43..9ea30756f1 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -548,7 +548,8 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - warn "Stopped at $sha1... $rest" + sha1_abbrev=$(git rev-parse --short $sha1) + warn "Stopped at $sha1_abbrev... $rest" exit_with_patch $sha1 0 ;; squash|s|fixup|f) diff --git a/git-rebase.sh b/git-rebase.sh index cf60c43908..0bf41ee72b 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -248,6 +248,7 @@ do ;; --exec=*) cmd="${cmd}exec ${1#--exec=}${LF}" + test -z "$interactive_rebase" && interactive_rebase=implied ;; --interactive) interactive_rebase=explicit @@ -348,12 +349,6 @@ do done test $# -gt 2 && usage -if test -n "$cmd" && - test "$interactive_rebase" != explicit -then - die "$(gettext "The --exec option must be used with the --interactive option")" -fi - if test -n "$action" then test -z "$in_progress" && die "$(gettext "No rebase in progress?")" diff --git a/git-send-email.perl b/git-send-email.perl index c45b22a19a..69587856df 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -19,10 +19,10 @@ use 5.008; use strict; use warnings; +use POSIX qw/strftime/; use Term::ReadLine; use Getopt::Long; use Text::ParseWords; -use Data::Dumper; use Term::ANSIColor; use File::Temp qw/ tempdir tempfile /; use File::Spec::Functions qw(catfile); @@ -827,9 +827,10 @@ sub file_declares_8bit_cte { # But it's a no-op to run sanitize_address on an already sanitized address. $sender = sanitize_address($sender); +my $to_whom = "To whom should the emails be sent (if anyone)?"; my $prompting = 0; if (!@initial_to && !defined $to_cmd) { - my $to = ask("Who should the emails be sent to (if any)? ", + my $to = ask("$to_whom ", default => "", valid_re => qr/\@.*\./, confirm_only => 1); push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later @@ -924,7 +925,7 @@ sub validate_address { cleanup_compose_files(); exit(0); } - $address = ask("Who should the email be sent to (if any)? ", + $address = ask("$to_whom ", default => "", valid_re => qr/\@.*\./, confirm_only => 1); } @@ -949,7 +950,7 @@ sub validate_address_list { sub make_message_id { my $uniq; if (!defined $message_id_stamp) { - $message_id_stamp = sprintf("%s-%s", time, $$); + $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time)); $message_id_serial = 0; } $message_id_serial++; @@ -964,7 +965,7 @@ sub make_message_id { require Sys::Hostname; $du_part = 'user@' . Sys::Hostname::hostname(); } - my $message_id_template = "<%s-git-send-email-%s>"; + my $message_id_template = "<%s-%s>"; $message_id = sprintf($message_id_template, $uniq, $du_part); #print "new message id = $message_id\n"; # Was useful for debugging } diff --git a/git-submodule.sh b/git-submodule.sh index 43c68deee9..2a84d7e66a 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -192,6 +192,16 @@ isnumber() n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" } +# Sanitize the local git environment for use within a submodule. We +# can't simply use clear_local_git_env since we want to preserve some +# of the settings from GIT_CONFIG_PARAMETERS. +sanitize_submodule_env() +{ + sanitized_config=$(git submodule--helper sanitize-config) + clear_local_git_env + GIT_CONFIG_PARAMETERS=$sanitized_config +} + # # Add a new submodule to the working tree, .gitmodules and the index # @@ -347,9 +357,9 @@ Use -f if you really want to add it." >&2 echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" fi fi - git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit + git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit ( - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && # ash fails to wordsplit ${branch:+-b "$branch"...} case "$branch" in @@ -413,12 +423,12 @@ cmd_foreach() die_if_unmatched "$mode" if test -e "$sm_path"/.git then - displaypath=$(relative_path "$sm_path") - say "$(eval_gettext "Entering '\$prefix\$displaypath'")" + displaypath=$(relative_path "$prefix$sm_path") + say "$(eval_gettext "Entering '\$displaypath'")" name=$(git submodule--helper name "$sm_path") ( prefix="$prefix$sm_path/" - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && sm_path=$(relative_path "$sm_path") && # we make $path available to scripts ... @@ -434,7 +444,7 @@ cmd_foreach() cmd_foreach "--recursive" "$@" fi ) <&3 3<&- || - die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")" + die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")" fi done } @@ -473,7 +483,7 @@ cmd_init() die_if_unmatched "$mode" name=$(git submodule--helper name "$sm_path") || exit - displaypath=$(relative_path "$sm_path") + displaypath=$(relative_path "$prefix$sm_path") # Copy url setting when it is not set yet if test -z "$(git config "submodule.$name.url")" @@ -592,14 +602,14 @@ cmd_deinit() } is_tip_reachable () ( - clear_local_git_env + sanitize_submodule_env && cd "$1" && rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) && test -z "$rev" ) fetch_in_submodule () ( - clear_local_git_env + sanitize_submodule_env && cd "$1" && case "$2" in '') @@ -663,6 +673,14 @@ cmd_update() --depth=*) depth=$1 ;; + -j|--jobs) + case "$2" in '') usage ;; esac + jobs="--jobs=$2" + shift + ;; + --jobs=*) + jobs=$1 + ;; --) shift break @@ -682,17 +700,21 @@ cmd_update() cmd_init "--" "$@" || return fi - cloned_modules= - git submodule--helper list --prefix "$wt_prefix" "$@" | { + { + git submodule--helper update-clone ${GIT_QUIET:+--quiet} \ + ${wt_prefix:+--prefix "$wt_prefix"} \ + ${prefix:+--recursive-prefix "$prefix"} \ + ${update:+--update "$update"} \ + ${reference:+--reference "$reference"} \ + ${depth:+--depth "$depth"} \ + ${jobs:+$jobs} \ + "$@" || echo "#unmatched" + } | { err= - while read mode sha1 stage sm_path + while read mode sha1 stage just_cloned sm_path do die_if_unmatched "$mode" - if test "$stage" = U - then - echo >&2 "Skipping unmerged submodule $prefix$sm_path" - continue - fi + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) branch=$(get_submodule_config "$name" branch master) @@ -709,29 +731,12 @@ cmd_update() displaypath=$(relative_path "$prefix$sm_path") - if test "$update_module" = "none" - then - echo "Skipping submodule '$displaypath'" - continue - fi - - if test -z "$url" + if test $just_cloned -eq 1 then - # Only mention uninitialized submodules when its - # path have been specified - test "$#" != "0" && - say "$(eval_gettext "Submodule path '\$displaypath' not initialized -Maybe you want to use 'update --init'?")" - continue - fi - - if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git - then - git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit - cloned_modules="$cloned_modules;$name" subsha1= + update_module=checkout else - subsha1=$(clear_local_git_env; cd "$sm_path" && + subsha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) || die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")" fi @@ -741,11 +746,11 @@ Maybe you want to use 'update --init'?")" if test -z "$nofetch" then # Fetch remote before determining tracking $sha1 - (clear_local_git_env; cd "$sm_path" && git-fetch) || + (sanitize_submodule_env; cd "$sm_path" && git-fetch) || die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")" fi - remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote) - sha1=$(clear_local_git_env; cd "$sm_path" && + remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote) + sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify "${remote_name}/${branch}") || die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")" fi @@ -774,13 +779,6 @@ Maybe you want to use 'update --init'?")" die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")" fi - # Is this something we just cloned? - case ";$cloned_modules;" in - *";$name;"*) - # then there is no local change to integrate - update_module=checkout ;; - esac - must_die_on_failure= case "$update_module" in checkout) @@ -802,15 +800,15 @@ Maybe you want to use 'update --init'?")" ;; !*) command="${update_module#!}" - die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$prefix\$sm_path'")" - say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': '\$command \$sha1'")" + die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")" + say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")" must_die_on_failure=yes ;; *) die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")" esac - if (clear_local_git_env; cd "$sm_path" && $command "$sha1") + if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1") then say "$say_msg" elif test -n "$must_die_on_failure" @@ -826,7 +824,7 @@ Maybe you want to use 'update --init'?")" then ( prefix="$prefix$sm_path/" - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && eval cmd_update ) @@ -864,7 +862,7 @@ Maybe you want to use 'update --init'?")" set_name_rev () { revname=$( ( - clear_local_git_env + sanitize_submodule_env cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || @@ -1148,7 +1146,7 @@ cmd_status() else if test -z "$cached" then - sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD) + sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) fi set_name_rev "$sm_path" "$sha1" say "+$sha1 $displaypath$revname" @@ -1158,7 +1156,8 @@ cmd_status() then ( prefix="$displaypath/" - clear_local_git_env + sanitize_submodule_env + wt_prefix= cd "$sm_path" && eval cmd_status ) || @@ -1232,7 +1231,7 @@ cmd_sync() if test -e "$sm_path"/.git then ( - clear_local_git_env + sanitize_submodule_env cd "$sm_path" remote=$(get_default_remote) git config remote."$remote".url "$sub_origin_url" diff --git a/gpg-interface.c b/gpg-interface.c index 3dc2fe397e..2259938236 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -237,6 +237,7 @@ int verify_signed_buffer(const char *payload, size_t payload_size, return error(_("could not run gpg.")); } + sigchain_push(SIGPIPE, SIG_IGN); write_in_full(gpg.in, payload, payload_size); close(gpg.in); @@ -250,6 +251,7 @@ int verify_signed_buffer(const char *payload, size_t payload_size, close(gpg.out); ret = finish_command(&gpg); + sigchain_pop(SIGPIPE); unlink_or_warn(path); diff --git a/http-backend.c b/http-backend.c index 8870a2681e..214881459d 100644 --- a/http-backend.c +++ b/http-backend.c @@ -484,9 +484,9 @@ static int show_head_ref(const char *refname, const struct object_id *oid, const char *target = resolve_ref_unsafe(refname, RESOLVE_REF_READING, unused.hash, NULL); - const char *target_nons = strip_namespace(target); - strbuf_addf(buf, "ref: %s\n", target_nons); + if (target) + strbuf_addf(buf, "ref: %s\n", strip_namespace(target)); } else { strbuf_addf(buf, "%s\n", oid_to_hex(oid)); } diff --git a/http.c b/http.c index 69da4454d8..4304b80ad3 100644 --- a/http.c +++ b/http.c @@ -605,7 +605,10 @@ static CURL *get_curl_handle(void) if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); #if LIBCURL_VERSION_NUM >= 0x071800 - if (starts_with(curl_http_proxy, "socks5")) + if (starts_with(curl_http_proxy, "socks5h")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); + else if (starts_with(curl_http_proxy, "socks5")) curl_easy_setopt(result, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); else if (starts_with(curl_http_proxy, "socks4a")) diff --git a/ident.c b/ident.c index 6e125821f0..4fd82d1043 100644 --- a/ident.c +++ b/ident.c @@ -351,15 +351,17 @@ const char *fmt_ident(const char *name, const char *email, if (want_name) { int using_default = 0; if (!name) { + if (strict && ident_use_config_only + && !(ident_config_given & IDENT_NAME_GIVEN)) { + fputs(env_hint, stderr); + die("no name was given and auto-detection is disabled"); + } name = ident_default_name(); using_default = 1; if (strict && default_name_is_bogus) { fputs(env_hint, stderr); die("unable to auto-detect name (got '%s')", name); } - if (strict && ident_use_config_only - && !(ident_config_given & IDENT_NAME_GIVEN)) - die("user.useConfigOnly set but no name given"); } if (!*name) { struct passwd *pw; @@ -374,14 +376,16 @@ const char *fmt_ident(const char *name, const char *email, } if (!email) { + if (strict && ident_use_config_only + && !(ident_config_given & IDENT_MAIL_GIVEN)) { + fputs(env_hint, stderr); + die("no email was given and auto-detection is disabled"); + } email = ident_default_email(); if (strict && default_email_is_bogus) { fputs(env_hint, stderr); die("unable to auto-detect email address (got '%s')", email); } - if (strict && ident_use_config_only - && !(ident_config_given & IDENT_MAIL_GIVEN)) - die("user.useConfigOnly set but no mail given"); } strbuf_reset(&ident); diff --git a/imap-send.c b/imap-send.c index 2c52027c84..938c691585 100644 --- a/imap-send.c +++ b/imap-send.c @@ -287,17 +287,20 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve SSL_library_init(); SSL_load_error_strings(); - if (use_tls_only) - meth = TLSv1_method(); - else - meth = SSLv23_method(); - + meth = SSLv23_method(); if (!meth) { ssl_socket_perror("SSLv23_method"); return -1; } ctx = SSL_CTX_new(meth); + if (!ctx) { + ssl_socket_perror("SSL_CTX_new"); + return -1; + } + + if (use_tls_only) + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); if (verify) SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); @@ -862,7 +865,6 @@ static char hexchar(unsigned int b) static char *cram(const char *challenge_64, const char *user, const char *pass) { int i, resp_len, encoded_len, decoded_len; - HMAC_CTX hmac; unsigned char hash[16]; char hex[33]; char *response, *response_64, *challenge; @@ -877,10 +879,8 @@ static char *cram(const char *challenge_64, const char *user, const char *pass) (unsigned char *)challenge_64, encoded_len); if (decoded_len < 0) die("invalid challenge %s", challenge_64); - HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5()); - HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len); - HMAC_Final(&hmac, hash, NULL); - HMAC_CTX_cleanup(&hmac); + if (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL)) + die("HMAC error"); hex[32] = 0; for (i = 0; i < 16; i++) { @@ -890,7 +890,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass) /* response: " " */ response = xstrfmt("%s %s", user, hex); - resp_len = strlen(response) + 1; + resp_len = strlen(response); response_64 = xmallocz(ENCODED_SIZE(resp_len)); encoded_len = EVP_EncodeBlock((unsigned char *)response_64, @@ -1095,11 +1095,6 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, char *f srvc->pass = xstrdup(cred.password); } - if (CAP(NOLOGIN)) { - fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host); - goto bail; - } - if (srvc->auth_method) { struct imap_cmd_cb cb; @@ -1123,6 +1118,11 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, char *f goto bail; } } else { + if (CAP(NOLOGIN)) { + fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", + srvc->user, srvc->host); + goto bail; + } if (!imap->buf.sock.ssl) imap_warn("*** IMAP Warning *** Password is being " "sent in the clear\n"); diff --git a/log-tree.c b/log-tree.c index 60f983934d..78a5381d0e 100644 --- a/log-tree.c +++ b/log-tree.c @@ -683,6 +683,7 @@ void show_log(struct rev_info *opt) ctx.fmt = opt->commit_format; ctx.mailmap = opt->mailmap; ctx.color = opt->diffopt.use_color; + ctx.expand_tabs_in_log = opt->expand_tabs_in_log; ctx.output_encoding = get_log_output_encoding(); if (opt->from_ident.mail_begin && opt->from_ident.name_begin) ctx.from_ident = &opt->from_ident; diff --git a/merge-recursive.c b/merge-recursive.c index b880ae50e7..06d31ed873 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -622,7 +622,7 @@ static char *unique_path(struct merge_options *o, const char *path, const char * base_len = newpath.len; while (string_list_has_string(&o->current_file_set, newpath.buf) || string_list_has_string(&o->current_directory_set, newpath.buf) || - file_exists(newpath.buf)) { + (!o->call_depth && file_exists(newpath.buf))) { strbuf_setlen(&newpath, base_len); strbuf_addf(&newpath, "_%d", suffix++); } @@ -1234,8 +1234,8 @@ static void conflict_rename_rename_2to1(struct merge_options *o, a->path, c1->path, ci->branch1, b->path, c2->path, ci->branch2); - remove_file(o, 1, a->path, would_lose_untracked(a->path)); - remove_file(o, 1, b->path, would_lose_untracked(b->path)); + remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path)); + remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path)); mfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other, o->branch1, c1->path, @@ -1773,8 +1773,6 @@ static int process_entry(struct merge_options *o, output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s"), conf, path, other_branch, path, new_path); - if (o->call_depth) - remove_file_from_cache(path); update_file(o, 0, sha, mode, new_path); if (o->call_depth) remove_file_from_cache(path); diff --git a/mergetools/examdiff b/mergetools/examdiff new file mode 100644 index 0000000000..7b524d4088 --- /dev/null +++ b/mergetools/examdiff @@ -0,0 +1,18 @@ +diff_cmd () { + "$merge_tool_path" "$LOCAL" "$REMOTE" -nh +} + +merge_cmd () { + touch "$BACKUP" + if $base_present + then + "$merge_tool_path" -merge "$LOCAL" "$BASE" "$REMOTE" -o:"$MERGED" -nh + else + "$merge_tool_path" -merge "$LOCAL" "$REMOTE" -o:"$MERGED" -nh + fi + check_unchanged +} + +translate_merge_tool_path() { + mergetool_find_win32_cmd "ExamDiff.com" "ExamDiff Pro" +} diff --git a/mergetools/winmerge b/mergetools/winmerge index 74a66d4e8d..f3819d316a 100644 --- a/mergetools/winmerge +++ b/mergetools/winmerge @@ -13,24 +13,5 @@ merge_cmd () { } translate_merge_tool_path() { - # Use WinMergeU.exe if it exists in $PATH - if type -p WinMergeU.exe >/dev/null 2>&1 - then - printf WinMergeU.exe - return - fi - - # Look for WinMergeU.exe in the typical locations - winmerge_exe="WinMerge/WinMergeU.exe" - for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' | - cut -d '=' -f 2- | sort -u) - do - if test -n "$directory" && test -x "$directory/$winmerge_exe" - then - printf '%s' "$directory/$winmerge_exe" - return - fi - done - - printf WinMergeU.exe + mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge" } diff --git a/path.c b/path.c index 969b494d72..bbaea5ab0b 100644 --- a/path.c +++ b/path.c @@ -702,17 +702,17 @@ static int calc_shared_perm(int mode) { int tweak; - if (shared_repository < 0) - tweak = -shared_repository; + if (get_shared_repository() < 0) + tweak = -get_shared_repository(); else - tweak = shared_repository; + tweak = get_shared_repository(); if (!(mode & S_IWUSR)) tweak &= ~0222; if (mode & S_IXUSR) /* Copy read bits to execute bits */ tweak |= (tweak & 0444) >> 2; - if (shared_repository < 0) + if (get_shared_repository() < 0) mode = (mode & ~0777) | tweak; else mode |= tweak; @@ -725,7 +725,7 @@ int adjust_shared_perm(const char *path) { int old_mode, new_mode; - if (!shared_repository) + if (!get_shared_repository()) return 0; if (get_st_mode_bits(path, &old_mode) < 0) return -1; diff --git a/po/fr.po b/po/fr.po index 88b0b8a78a..55ca387ba4 100644 --- a/po/fr.po +++ b/po/fr.po @@ -2945,7 +2945,7 @@ msgstr "utiliser l'horodatage actuel pour la date d'auteur" #: builtin/am.c:2321 builtin/commit.c:1593 builtin/merge.c:225 #: builtin/pull.c:159 builtin/revert.c:92 builtin/tag.c:355 msgid "key-id" -msgstr "id de clé" +msgstr "id-clé" #: builtin/am.c:2322 msgid "GPG-sign commits" @@ -4545,7 +4545,7 @@ msgstr "style" #: builtin/checkout.c:1154 msgid "conflict style (merge or diff3)" -msgstr "style de conflit (fusion ou diff3)" +msgstr "style de conflit (merge (fusion) ou diff3)" #: builtin/checkout.c:1157 msgid "do not limit pathspecs to sparse entries only" @@ -6197,7 +6197,7 @@ msgstr "convertir en un dépôt complet" #: builtin/fetch.c:122 builtin/log.c:1236 msgid "dir" -msgstr "dir" +msgstr "répertoire" #: builtin/fetch.c:123 msgid "prepend this to submodule path output" @@ -10809,11 +10809,11 @@ msgstr "git show-ref --exclude-existing[=]" #: builtin/show-ref.c:165 msgid "only show tags (can be combined with heads)" -msgstr "afficher seulement les étiquettes (peut être combiné avec des têtes)" +msgstr "afficher seulement les étiquettes (peut être combiné avec heads)" #: builtin/show-ref.c:166 msgid "only show heads (can be combined with tags)" -msgstr "afficher seulement les têtes (peut être combiné avec des étiquettes)" +msgstr "afficher seulement les têtes (peut être combiné avec tags)" #: builtin/show-ref.c:167 msgid "stricter reference checking, requires exact ref path" diff --git a/pretty.c b/pretty.c index 92b2870a7e..87c44971a1 100644 --- a/pretty.c +++ b/pretty.c @@ -16,6 +16,7 @@ static struct cmt_fmt_map { const char *name; enum cmit_fmt format; int is_tformat; + int expand_tabs_in_log; int is_alias; const char *user_format; } *commit_formats; @@ -87,13 +88,13 @@ static int git_pretty_formats_config(const char *var, const char *value, void *c static void setup_commit_formats(void) { struct cmt_fmt_map builtin_formats[] = { - { "raw", CMIT_FMT_RAW, 0 }, - { "medium", CMIT_FMT_MEDIUM, 0 }, - { "short", CMIT_FMT_SHORT, 0 }, - { "email", CMIT_FMT_EMAIL, 0 }, - { "fuller", CMIT_FMT_FULLER, 0 }, - { "full", CMIT_FMT_FULL, 0 }, - { "oneline", CMIT_FMT_ONELINE, 1 } + { "raw", CMIT_FMT_RAW, 0, 0 }, + { "medium", CMIT_FMT_MEDIUM, 0, 8 }, + { "short", CMIT_FMT_SHORT, 0, 0 }, + { "email", CMIT_FMT_EMAIL, 0, 0 }, + { "fuller", CMIT_FMT_FULLER, 0, 8 }, + { "full", CMIT_FMT_FULL, 0, 8 }, + { "oneline", CMIT_FMT_ONELINE, 1, 0 } }; commit_formats_len = ARRAY_SIZE(builtin_formats); builtin_formats_len = commit_formats_len; @@ -172,6 +173,7 @@ void get_commit_format(const char *arg, struct rev_info *rev) rev->commit_format = commit_format->format; rev->use_terminator = commit_format->is_tformat; + rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log; if (commit_format->format == CMIT_FMT_USERFORMAT) { save_user_format(rev, commit_format->user_format, commit_format->is_tformat); @@ -1629,6 +1631,72 @@ void pp_title_line(struct pretty_print_context *pp, strbuf_release(&title); } +static int pp_utf8_width(const char *start, const char *end) +{ + int width = 0; + size_t remain = end - start; + + while (remain) { + int n = utf8_width(&start, &remain); + if (n < 0 || !start) + return -1; + width += n; + } + return width; +} + +static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth, + const char *line, int linelen) +{ + const char *tab; + + while ((tab = memchr(line, '\t', linelen)) != NULL) { + int width = pp_utf8_width(line, tab); + + /* + * If it wasn't well-formed utf8, or it + * had characters with badly defined + * width (control characters etc), just + * give up on trying to align things. + */ + if (width < 0) + break; + + /* Output the data .. */ + strbuf_add(sb, line, tab - line); + + /* .. and the de-tabified tab */ + strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth)); + + /* Skip over the printed part .. */ + linelen -= tab + 1 - line; + line = tab + 1; + } + + /* + * Print out everything after the last tab without + * worrying about width - there's nothing more to + * align. + */ + strbuf_add(sb, line, linelen); +} + +/* + * pp_handle_indent() prints out the intendation, and + * the whole line (without the final newline), after + * de-tabifying. + */ +static void pp_handle_indent(struct pretty_print_context *pp, + struct strbuf *sb, int indent, + const char *line, int linelen) +{ + strbuf_addchars(sb, ' ', indent); + if (pp->expand_tabs_in_log) + strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, line, linelen); + else + strbuf_add(sb, line, linelen); +} + void pp_remainder(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, @@ -1653,8 +1721,12 @@ void pp_remainder(struct pretty_print_context *pp, strbuf_grow(sb, linelen + indent + 20); if (indent) - strbuf_addchars(sb, ' ', indent); - strbuf_add(sb, line, linelen); + pp_handle_indent(pp, sb, indent, line, linelen); + else if (pp->expand_tabs_in_log) + strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, + line, linelen); + else + strbuf_add(sb, line, linelen); strbuf_addch(sb, '\n'); } } diff --git a/quote.c b/quote.c index fe884d2452..b281a8fe45 100644 --- a/quote.c +++ b/quote.c @@ -43,6 +43,19 @@ void sq_quote_buf(struct strbuf *dst, const char *src) free(to_free); } +void sq_quotef(struct strbuf *dst, const char *fmt, ...) +{ + struct strbuf src = STRBUF_INIT; + + va_list ap; + va_start(ap, fmt); + strbuf_vaddf(&src, fmt, ap); + va_end(ap); + + sq_quote_buf(dst, src.buf); + strbuf_release(&src); +} + void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) { int i; diff --git a/quote.h b/quote.h index 99e04d34bf..6c53a2cc66 100644 --- a/quote.h +++ b/quote.h @@ -25,10 +25,13 @@ struct strbuf; * sq_quote_buf() writes to an existing buffer of specified size; it * will return the number of characters that would have been written * excluding the final null regardless of the buffer size. + * + * sq_quotef() quotes the entire formatted string as a single result. */ extern void sq_quote_buf(struct strbuf *, const char *src); extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); +extern void sq_quotef(struct strbuf *, const char *fmt, ...); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/refs.c b/refs.c index b0e6ece6f4..87dc82f1d8 100644 --- a/refs.c +++ b/refs.c @@ -1080,3 +1080,152 @@ int rename_ref_available(const char *oldname, const char *newname) strbuf_release(&err); return ret; } + +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + struct object_id oid; + int flag; + + if (submodule) { + if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0) + return fn("HEAD", &oid, 0, cb_data); + + return 0; + } + + if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag)) + return fn("HEAD", &oid, flag, cb_data); + + return 0; +} + +int head_ref(each_ref_fn fn, void *cb_data) +{ + return head_ref_submodule(NULL, fn, cb_data); +} + +int for_each_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(NULL, "", fn, 0, 0, cb_data); +} + +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, "", fn, 0, 0, cb_data); +} + +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) +{ + unsigned int flag = 0; + + if (broken) + flag = DO_FOR_EACH_INCLUDE_BROKEN; + return do_for_each_ref(NULL, prefix, fn, 0, flag, cb_data); +} + +int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_replace_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(NULL, git_replace_ref_base, fn, + strlen(git_replace_ref_base), 0, cb_data); +} + +int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) +{ + struct strbuf buf = STRBUF_INIT; + int ret; + strbuf_addf(&buf, "%srefs/", get_git_namespace()); + ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data); + strbuf_release(&buf); + return ret; +} + +int for_each_rawref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(NULL, "", fn, 0, + DO_FOR_EACH_INCLUDE_BROKEN, cb_data); +} + +/* This function needs to return a meaningful errno on failure */ +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags) +{ + static struct strbuf sb_refname = STRBUF_INIT; + int unused_flags; + int symref_count; + + if (!flags) + flags = &unused_flags; + + *flags = 0; + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(refname)) { + errno = EINVAL; + return NULL; + } + + /* + * dwim_ref() uses REF_ISBROKEN to distinguish between + * missing refs and refs that were present but invalid, + * to complain about the latter to stderr. + * + * We don't know whether the ref exists, so don't set + * REF_ISBROKEN yet. + */ + *flags |= REF_BAD_NAME; + } + + for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) { + unsigned int read_flags = 0; + + if (read_raw_ref(refname, sha1, &sb_refname, &read_flags)) { + *flags |= read_flags; + if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING)) + return NULL; + hashclr(sha1); + if (*flags & REF_BAD_NAME) + *flags |= REF_ISBROKEN; + return refname; + } + + *flags |= read_flags; + + if (!(read_flags & REF_ISSYMREF)) { + if (*flags & REF_BAD_NAME) { + hashclr(sha1); + *flags |= REF_ISBROKEN; + } + return refname; + } + + refname = sb_refname.buf; + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(refname)) { + errno = EINVAL; + return NULL; + } + + *flags |= REF_ISBROKEN | REF_BAD_NAME; + } + } + + errno = ELOOP; + return NULL; +} diff --git a/refs.h b/refs.h index 2f3decb432..9230d47142 100644 --- a/refs.h +++ b/refs.h @@ -306,6 +306,15 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg extern int create_symref(const char *refname, const char *target, const char *logmsg); +/* + * Update HEAD of the specified gitdir. + * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but + * this can update the main working tree's HEAD regardless of where + * $GIT_DIR points to. + * Return 0 if successful, non-zero otherwise. + * */ +extern int set_worktree_head_symref(const char *gitdir, const char *target); + enum action_on_err { UPDATE_REFS_MSG_ON_ERR, UPDATE_REFS_DIE_ON_ERR, diff --git a/refs/files-backend.c b/refs/files-backend.c index 81f68f846b..1f38076411 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -513,9 +513,6 @@ static void sort_ref_dir(struct ref_dir *dir) dir->sorted = dir->nr = i; } -/* Include broken references in a do_for_each_ref*() iteration: */ -#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 - /* * Return true iff the reference described by entry can be resolved to * an object in the database. Emit a warning if the referred-to @@ -1272,8 +1269,6 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs) return get_ref_dir(refs->loose); } -/* We allow "recursive" symbolic refs. Only within reason, though */ -#define MAXDEPTH 5 #define MAXREFLEN (1024) /* @@ -1303,7 +1298,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs, char buffer[128], *p; char *path; - if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) + if (recursion > SYMREF_MAXDEPTH || strlen(refname) > MAXREFLEN) return -1; path = *refs->name ? git_pathdup_submodule(refs->name, "%s", refname) @@ -1371,13 +1366,11 @@ static struct ref_entry *get_packed_ref(const char *refname) } /* - * A loose ref file doesn't exist; check for a packed ref. The - * options are forwarded from resolve_safe_unsafe(). + * A loose ref file doesn't exist; check for a packed ref. */ static int resolve_missing_loose_ref(const char *refname, - int resolve_flags, unsigned char *sha1, - int *flags) + unsigned int *flags) { struct ref_entry *entry; @@ -1388,205 +1381,158 @@ static int resolve_missing_loose_ref(const char *refname, entry = get_packed_ref(refname); if (entry) { hashcpy(sha1, entry->u.value.oid.hash); - if (flags) - *flags |= REF_ISPACKED; - return 0; - } - /* The reference is not a packed reference, either. */ - if (resolve_flags & RESOLVE_REF_READING) { - errno = ENOENT; - return -1; - } else { - hashclr(sha1); + *flags |= REF_ISPACKED; return 0; } + /* refname is not a packed reference. */ + return -1; } -/* This function needs to return a meaningful errno on failure */ -static const char *resolve_ref_1(const char *refname, - int resolve_flags, - unsigned char *sha1, - int *flags, - struct strbuf *sb_refname, - struct strbuf *sb_path, - struct strbuf *sb_contents) +/* + * Read a raw ref from the filesystem or packed refs file. + * + * If the ref is a sha1, fill in sha1 and return 0. + * + * If the ref is symbolic, fill in *symref with the referrent + * (e.g. "refs/heads/master") and return 0. The caller is responsible + * for validating the referrent. Set REF_ISSYMREF in flags. + * + * If the ref doesn't exist, set errno to ENOENT and return -1. + * + * If the ref exists but is neither a symbolic ref nor a sha1, it is + * broken. Set REF_ISBROKEN in flags, set errno to EINVAL, and return + * -1. + * + * If there is another error reading the ref, set errno appropriately and + * return -1. + * + * Backend-specific flags might be set in flags as well, regardless of + * outcome. + * + * sb_path is workspace: the caller should allocate and free it. + * + * It is OK for refname to point into symref. In this case: + * - if the function succeeds with REF_ISSYMREF, symref will be + * overwritten and the memory pointed to by refname might be changed + * or even freed. + * - in all other cases, symref will be untouched, and therefore + * refname will still be valid and unchanged. + */ +int read_raw_ref(const char *refname, unsigned char *sha1, + struct strbuf *symref, unsigned int *flags) { - int depth = MAXDEPTH; - int bad_name = 0; + struct strbuf sb_contents = STRBUF_INIT; + struct strbuf sb_path = STRBUF_INIT; + const char *path; + const char *buf; + struct stat st; + int fd; + int ret = -1; + int save_errno; - if (flags) - *flags = 0; + strbuf_reset(&sb_path); + strbuf_git_path(&sb_path, "%s", refname); + path = sb_path.buf; - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - if (flags) - *flags |= REF_BAD_NAME; +stat_ref: + /* + * We might have to loop back here to avoid a race + * condition: first we lstat() the file, then we try + * to read it as a link or as a file. But if somebody + * changes the type of the file (file <-> directory + * <-> symlink) between the lstat() and reading, then + * we don't want to report that as an error but rather + * try again starting with the lstat(). + */ - if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - errno = EINVAL; - return NULL; + if (lstat(path, &st) < 0) { + if (errno != ENOENT) + goto out; + if (resolve_missing_loose_ref(refname, sha1, flags)) { + errno = ENOENT; + goto out; } - /* - * dwim_ref() uses REF_ISBROKEN to distinguish between - * missing refs and refs that were present but invalid, - * to complain about the latter to stderr. - * - * We don't know whether the ref exists, so don't set - * REF_ISBROKEN yet. - */ - bad_name = 1; + ret = 0; + goto out; } - for (;;) { - const char *path; - struct stat st; - char *buf; - int fd; - - if (--depth < 0) { - errno = ELOOP; - return NULL; - } - - strbuf_reset(sb_path); - strbuf_git_path(sb_path, "%s", refname); - path = sb_path->buf; - /* - * We might have to loop back here to avoid a race - * condition: first we lstat() the file, then we try - * to read it as a link or as a file. But if somebody - * changes the type of the file (file <-> directory - * <-> symlink) between the lstat() and reading, then - * we don't want to report that as an error but rather - * try again starting with the lstat(). - */ - stat_ref: - if (lstat(path, &st) < 0) { - if (errno != ENOENT) - return NULL; - if (resolve_missing_loose_ref(refname, resolve_flags, - sha1, flags)) - return NULL; - if (bad_name) { - hashclr(sha1); - if (flags) - *flags |= REF_ISBROKEN; - } - return refname; - } - - /* Follow "normalized" - ie "refs/.." symlinks by hand */ - if (S_ISLNK(st.st_mode)) { - strbuf_reset(sb_contents); - if (strbuf_readlink(sb_contents, path, 0) < 0) { - if (errno == ENOENT || errno == EINVAL) - /* inconsistent with lstat; retry */ - goto stat_ref; - else - return NULL; - } - if (starts_with(sb_contents->buf, "refs/") && - !check_refname_format(sb_contents->buf, 0)) { - strbuf_swap(sb_refname, sb_contents); - refname = sb_refname->buf; - if (flags) - *flags |= REF_ISSYMREF; - if (resolve_flags & RESOLVE_REF_NO_RECURSE) { - hashclr(sha1); - return refname; - } - continue; - } - } - - /* Is it a directory? */ - if (S_ISDIR(st.st_mode)) { - errno = EISDIR; - return NULL; - } - - /* - * Anything else, just open it and try to use it as - * a ref - */ - fd = open(path, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT) + /* Follow "normalized" - ie "refs/.." symlinks by hand */ + if (S_ISLNK(st.st_mode)) { + strbuf_reset(&sb_contents); + if (strbuf_readlink(&sb_contents, path, 0) < 0) { + if (errno == ENOENT || errno == EINVAL) /* inconsistent with lstat; retry */ goto stat_ref; else - return NULL; + goto out; } - strbuf_reset(sb_contents); - if (strbuf_read(sb_contents, fd, 256) < 0) { - int save_errno = errno; - close(fd); - errno = save_errno; - return NULL; + if (starts_with(sb_contents.buf, "refs/") && + !check_refname_format(sb_contents.buf, 0)) { + strbuf_swap(&sb_contents, symref); + *flags |= REF_ISSYMREF; + ret = 0; + goto out; } - close(fd); - strbuf_rtrim(sb_contents); + } - /* - * Is it a symbolic ref? - */ - if (!starts_with(sb_contents->buf, "ref:")) { - /* - * Please note that FETCH_HEAD has a second - * line containing other data. - */ - if (get_sha1_hex(sb_contents->buf, sha1) || - (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) { - if (flags) - *flags |= REF_ISBROKEN; - errno = EINVAL; - return NULL; - } - if (bad_name) { - hashclr(sha1); - if (flags) - *flags |= REF_ISBROKEN; - } - return refname; - } - if (flags) - *flags |= REF_ISSYMREF; - buf = sb_contents->buf + 4; + /* Is it a directory? */ + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + goto out; + } + + /* + * Anything else, just open it and try to use it as + * a ref + */ + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + /* inconsistent with lstat; retry */ + goto stat_ref; + else + goto out; + } + strbuf_reset(&sb_contents); + if (strbuf_read(&sb_contents, fd, 256) < 0) { + int save_errno = errno; + close(fd); + errno = save_errno; + goto out; + } + close(fd); + strbuf_rtrim(&sb_contents); + buf = sb_contents.buf; + if (starts_with(buf, "ref:")) { + buf += 4; while (isspace(*buf)) buf++; - strbuf_reset(sb_refname); - strbuf_addstr(sb_refname, buf); - refname = sb_refname->buf; - if (resolve_flags & RESOLVE_REF_NO_RECURSE) { - hashclr(sha1); - return refname; - } - if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { - if (flags) - *flags |= REF_ISBROKEN; - - if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(buf)) { - errno = EINVAL; - return NULL; - } - bad_name = 1; - } + + strbuf_reset(symref); + strbuf_addstr(symref, buf); + *flags |= REF_ISSYMREF; + ret = 0; + goto out; } -} -const char *resolve_ref_unsafe(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags) -{ - static struct strbuf sb_refname = STRBUF_INIT; - struct strbuf sb_contents = STRBUF_INIT; - struct strbuf sb_path = STRBUF_INIT; - const char *ret; + /* + * Please note that FETCH_HEAD has additional + * data after the sha. + */ + if (get_sha1_hex(buf, sha1) || + (buf[40] != '\0' && !isspace(buf[40]))) { + *flags |= REF_ISBROKEN; + errno = EINVAL; + goto out; + } + + ret = 0; - ret = resolve_ref_1(refname, resolve_flags, sha1, flags, - &sb_refname, &sb_path, &sb_contents); +out: + save_errno = errno; strbuf_release(&sb_path); strbuf_release(&sb_contents); + errno = save_errno; return ret; } @@ -1727,10 +1673,13 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base, * value, stop the iteration and return that value; otherwise, return * 0. */ -static int do_for_each_ref(struct ref_cache *refs, const char *base, - each_ref_fn fn, int trim, int flags, void *cb_data) +int do_for_each_ref(const char *submodule, const char *base, + each_ref_fn fn, int trim, int flags, void *cb_data) { struct ref_entry_cb data; + struct ref_cache *refs; + + refs = get_ref_cache(submodule); data.base = base; data.trim = trim; data.flags = flags; @@ -1745,86 +1694,6 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base, return do_for_each_entry(refs, base, do_one_ref, &data); } -static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) -{ - struct object_id oid; - int flag; - - if (submodule) { - if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0) - return fn("HEAD", &oid, 0, cb_data); - - return 0; - } - - if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag)) - return fn("HEAD", &oid, flag, cb_data); - - return 0; -} - -int head_ref(each_ref_fn fn, void *cb_data) -{ - return do_head_ref(NULL, fn, cb_data); -} - -int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return do_head_ref(submodule, fn, cb_data); -} - -int for_each_ref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data); -} - -int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data); -} - -int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); -} - -int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) -{ - unsigned int flag = 0; - - if (broken) - flag = DO_FOR_EACH_INCLUDE_BROKEN; - return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data); -} - -int for_each_ref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data); -} - -int for_each_replace_ref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, git_replace_ref_base, fn, - strlen(git_replace_ref_base), 0, cb_data); -} - -int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) -{ - struct strbuf buf = STRBUF_INIT; - int ret; - strbuf_addf(&buf, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data); - strbuf_release(&buf); - return ret; -} - -int for_each_rawref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, "", fn, 0, - DO_FOR_EACH_INCLUDE_BROKEN, cb_data); -} - static void unlock_ref(struct ref_lock *lock) { /* Do not free lock->lk -- atexit() still looks at them */ @@ -2894,6 +2763,42 @@ int create_symref(const char *refname, const char *target, const char *logmsg) return ret; } +int set_worktree_head_symref(const char *gitdir, const char *target) +{ + static struct lock_file head_lock; + struct ref_lock *lock; + struct strbuf head_path = STRBUF_INIT; + const char *head_rel; + int ret; + + strbuf_addf(&head_path, "%s/HEAD", absolute_path(gitdir)); + if (hold_lock_file_for_update(&head_lock, head_path.buf, + LOCK_NO_DEREF) < 0) { + struct strbuf err = STRBUF_INIT; + unable_to_lock_message(head_path.buf, errno, &err); + error("%s", err.buf); + strbuf_release(&err); + strbuf_release(&head_path); + return -1; + } + + /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for + linked trees */ + head_rel = remove_leading_path(head_path.buf, + absolute_path(get_git_common_dir())); + /* to make use of create_symref_locked(), initialize ref_lock */ + lock = xcalloc(1, sizeof(struct ref_lock)); + lock->lk = &head_lock; + lock->ref_name = xstrdup(head_rel); + lock->orig_ref_name = xstrdup(head_rel); + + ret = create_symref_locked(lock, head_rel, target, NULL); + + unlock_ref(lock); /* will free lock */ + strbuf_release(&head_path); + return ret; +} + int reflog_exists(const char *refname) { struct stat st; @@ -3445,7 +3350,8 @@ int reflog_expire(const char *refname, const unsigned char *sha1, * reference itself, plus we might need to update the * reference if --updateref was specified: */ - lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err); + lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF, + &type, &err); if (!lock) { error("cannot lock ref '%s': %s", refname, err.buf); strbuf_release(&err); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index c7dded35f4..3a4f634cb4 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -197,4 +197,19 @@ const char *find_descendant_ref(const char *dirname, int rename_ref_available(const char *oldname, const char *newname); +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define SYMREF_MAXDEPTH 5 + +/* Include broken references in a do_for_each_ref*() iteration: */ +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 + +/* + * The common backend for the for_each_*ref* functions + */ +int do_for_each_ref(const char *submodule, const char *base, + each_ref_fn fn, int trim, int flags, void *cb_data); + +int read_raw_ref(const char *refname, unsigned char *sha1, + struct strbuf *symref, unsigned int *flags); + #endif /* REFS_REFS_INTERNAL_H */ diff --git a/remote.c b/remote.c index 28fd676acb..ddc4f8fd48 100644 --- a/remote.c +++ b/remote.c @@ -1660,7 +1660,7 @@ int branch_merge_matches(struct branch *branch, return refname_match(branch->merge[i]->src, refname); } -__attribute((format (printf,2,3))) +__attribute__((format (printf,2,3))) static const char *error_buf(struct strbuf *err, const char *fmt, ...) { if (err) { diff --git a/rerere.c b/rerere.c index 587b7e2717..c8b9f40787 100644 --- a/rerere.c +++ b/rerere.c @@ -8,6 +8,7 @@ #include "ll-merge.h" #include "attr.h" #include "pathspec.h" +#include "sha1-lookup.h" #define RESOLVED 0 #define PUNTED 1 @@ -20,6 +21,29 @@ static int rerere_enabled = -1; /* automatically update cleanly resolved paths to the index */ static int rerere_autoupdate; +static int rerere_dir_nr; +static int rerere_dir_alloc; + +#define RR_HAS_POSTIMAGE 1 +#define RR_HAS_PREIMAGE 2 +static struct rerere_dir { + unsigned char sha1[20]; + int status_alloc, status_nr; + unsigned char *status; +} **rerere_dir; + +static void free_rerere_dirs(void) +{ + int i; + for (i = 0; i < rerere_dir_nr; i++) { + free(rerere_dir[i]->status); + free(rerere_dir[i]); + } + free(rerere_dir); + rerere_dir_nr = rerere_dir_alloc = 0; + rerere_dir = NULL; +} + static void free_rerere_id(struct string_list_item *item) { free(item->util); @@ -27,7 +51,33 @@ static void free_rerere_id(struct string_list_item *item) static const char *rerere_id_hex(const struct rerere_id *id) { - return id->hex; + return sha1_to_hex(id->collection->sha1); +} + +static void fit_variant(struct rerere_dir *rr_dir, int variant) +{ + variant++; + ALLOC_GROW(rr_dir->status, variant, rr_dir->status_alloc); + if (rr_dir->status_nr < variant) { + memset(rr_dir->status + rr_dir->status_nr, + '\0', variant - rr_dir->status_nr); + rr_dir->status_nr = variant; + } +} + +static void assign_variant(struct rerere_id *id) +{ + int variant; + struct rerere_dir *rr_dir = id->collection; + + variant = id->variant; + if (variant < 0) { + for (variant = 0; variant < rr_dir->status_nr; variant++) + if (!rr_dir->status[variant]) + break; + } + fit_variant(rr_dir, variant); + id->variant = variant; } const char *rerere_path(const struct rerere_id *id, const char *file) @@ -35,20 +85,103 @@ const char *rerere_path(const struct rerere_id *id, const char *file) if (!file) return git_path("rr-cache/%s", rerere_id_hex(id)); - return git_path("rr-cache/%s/%s", rerere_id_hex(id), file); + if (id->variant <= 0) + return git_path("rr-cache/%s/%s", rerere_id_hex(id), file); + + return git_path("rr-cache/%s/%s.%d", + rerere_id_hex(id), file, id->variant); +} + +static int is_rr_file(const char *name, const char *filename, int *variant) +{ + const char *suffix; + char *ep; + + if (!strcmp(name, filename)) { + *variant = 0; + return 1; + } + if (!skip_prefix(name, filename, &suffix) || *suffix != '.') + return 0; + + errno = 0; + *variant = strtol(suffix + 1, &ep, 10); + if (errno || *ep) + return 0; + return 1; +} + +static void scan_rerere_dir(struct rerere_dir *rr_dir) +{ + struct dirent *de; + DIR *dir = opendir(git_path("rr-cache/%s", sha1_to_hex(rr_dir->sha1))); + + if (!dir) + return; + while ((de = readdir(dir)) != NULL) { + int variant; + + if (is_rr_file(de->d_name, "postimage", &variant)) { + fit_variant(rr_dir, variant); + rr_dir->status[variant] |= RR_HAS_POSTIMAGE; + } else if (is_rr_file(de->d_name, "preimage", &variant)) { + fit_variant(rr_dir, variant); + rr_dir->status[variant] |= RR_HAS_PREIMAGE; + } + } + closedir(dir); +} + +static const unsigned char *rerere_dir_sha1(size_t i, void *table) +{ + struct rerere_dir **rr_dir = table; + return rr_dir[i]->sha1; +} + +static struct rerere_dir *find_rerere_dir(const char *hex) +{ + unsigned char sha1[20]; + struct rerere_dir *rr_dir; + int pos; + + if (get_sha1_hex(hex, sha1)) + return NULL; /* BUG */ + pos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1); + if (pos < 0) { + rr_dir = xmalloc(sizeof(*rr_dir)); + hashcpy(rr_dir->sha1, sha1); + rr_dir->status = NULL; + rr_dir->status_nr = 0; + rr_dir->status_alloc = 0; + pos = -1 - pos; + + /* Make sure the array is big enough ... */ + ALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc); + /* ... and add it in. */ + rerere_dir_nr++; + memmove(rerere_dir + pos + 1, rerere_dir + pos, + (rerere_dir_nr - pos - 1) * sizeof(*rerere_dir)); + rerere_dir[pos] = rr_dir; + scan_rerere_dir(rr_dir); + } + return rerere_dir[pos]; } static int has_rerere_resolution(const struct rerere_id *id) { - struct stat st; + const int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE; + int variant = id->variant; - return !stat(rerere_path(id, "postimage"), &st); + if (variant < 0) + return 0; + return ((id->collection->status[variant] & both) == both); } static struct rerere_id *new_rerere_id_hex(char *hex) { struct rerere_id *id = xmalloc(sizeof(*id)); - xsnprintf(id->hex, sizeof(id->hex), "%s", hex); + id->collection = find_rerere_dir(hex); + id->variant = -1; /* not known yet */ return id; } @@ -75,16 +208,26 @@ static void read_rr(struct string_list *rr) char *path; unsigned char sha1[20]; struct rerere_id *id; + int variant; /* There has to be the hash, tab, path and then NUL */ if (buf.len < 42 || get_sha1_hex(buf.buf, sha1)) die("corrupt MERGE_RR"); - if (buf.buf[40] != '\t') + if (buf.buf[40] != '.') { + variant = 0; + path = buf.buf + 40; + } else { + errno = 0; + variant = strtol(buf.buf + 41, &path, 10); + if (errno) + die("corrupt MERGE_RR"); + } + if (*(path++) != '\t') die("corrupt MERGE_RR"); buf.buf[40] = '\0'; - path = buf.buf + 41; id = new_rerere_id_hex(buf.buf); + id->variant = variant; string_list_insert(rr, path)->util = id; } strbuf_release(&buf); @@ -105,9 +248,16 @@ static int write_rr(struct string_list *rr, int out_fd) id = rr->items[i].util; if (!id) continue; - strbuf_addf(&buf, "%s\t%s%c", - rerere_id_hex(id), - rr->items[i].string, 0); + assert(id->variant >= 0); + if (0 < id->variant) + strbuf_addf(&buf, "%s.%d\t%s%c", + rerere_id_hex(id), id->variant, + rr->items[i].string, 0); + else + strbuf_addf(&buf, "%s\t%s%c", + rerere_id_hex(id), + rr->items[i].string, 0); + if (write_in_full(out_fd, buf.buf, buf.len) != buf.len) die("unable to write rerere record"); @@ -364,103 +514,6 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output return hunk_no; } -/* - * Subclass of rerere_io that reads from an in-core buffer that is a - * strbuf - */ -struct rerere_io_mem { - struct rerere_io io; - struct strbuf input; -}; - -/* - * ... and its getline() method implementation - */ -static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) -{ - struct rerere_io_mem *io = (struct rerere_io_mem *)io_; - char *ep; - size_t len; - - strbuf_release(sb); - if (!io->input.len) - return -1; - ep = memchr(io->input.buf, '\n', io->input.len); - if (!ep) - ep = io->input.buf + io->input.len; - else if (*ep == '\n') - ep++; - len = ep - io->input.buf; - strbuf_add(sb, io->input.buf, len); - strbuf_remove(&io->input, 0, len); - return 0; -} - -static int handle_cache(const char *path, unsigned char *sha1, const char *output) -{ - mmfile_t mmfile[3] = {{NULL}}; - mmbuffer_t result = {NULL, 0}; - const struct cache_entry *ce; - int pos, len, i, hunk_no; - struct rerere_io_mem io; - int marker_size = ll_merge_marker_size(path); - - /* - * Reproduce the conflicted merge in-core - */ - len = strlen(path); - pos = cache_name_pos(path, len); - if (0 <= pos) - return -1; - pos = -pos - 1; - - while (pos < active_nr) { - enum object_type type; - unsigned long size; - - ce = active_cache[pos++]; - if (ce_namelen(ce) != len || memcmp(ce->name, path, len)) - break; - i = ce_stage(ce) - 1; - if (!mmfile[i].ptr) { - mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); - mmfile[i].size = size; - } - } - for (i = 0; i < 3; i++) - if (!mmfile[i].ptr && !mmfile[i].size) - mmfile[i].ptr = xstrdup(""); - - /* - * NEEDSWORK: handle conflicts from merges with - * merge.renormalize set, too - */ - ll_merge(&result, path, &mmfile[0], NULL, - &mmfile[1], "ours", - &mmfile[2], "theirs", NULL); - for (i = 0; i < 3; i++) - free(mmfile[i].ptr); - - memset(&io, 0, sizeof(io)); - io.io.getline = rerere_mem_getline; - if (output) - io.io.output = fopen(output, "w"); - else - io.io.output = NULL; - strbuf_init(&io.input, 0); - strbuf_attach(&io.input, result.ptr, result.size, result.size); - - /* - * Grab the conflict ID and optionally write the original - * contents with conflict markers out. - */ - hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size); - strbuf_release(&io.input); - if (io.io.output) - fclose(io.io.output); - return hunk_no; -} - /* * Look at a cache entry at "i" and see if it is not conflicting, * conflicting and we are willing to handle, or conflicting and @@ -568,6 +621,33 @@ int rerere_remaining(struct string_list *merge_rr) return 0; } +/* + * Try using the given conflict resolution "ID" to see + * if that recorded conflict resolves cleanly what we + * got in the "cur". + */ +static int try_merge(const struct rerere_id *id, const char *path, + mmfile_t *cur, mmbuffer_t *result) +{ + int ret; + mmfile_t base = {NULL, 0}, other = {NULL, 0}; + + if (read_mmfile(&base, rerere_path(id, "preimage")) || + read_mmfile(&other, rerere_path(id, "postimage"))) + ret = 1; + else + /* + * A three-way merge. Note that this honors user-customizable + * low-level merge driver settings. + */ + ret = ll_merge(result, path, &base, NULL, cur, "", &other, "", NULL); + + free(base.ptr); + free(other.ptr); + + return ret; +} + /* * Find the conflict identified by "id"; the change between its * "preimage" (i.e. a previous contents with conflict markers) and its @@ -582,30 +662,20 @@ static int merge(const struct rerere_id *id, const char *path) { FILE *f; int ret; - mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0}; + mmfile_t cur = {NULL, 0}; mmbuffer_t result = {NULL, 0}; /* * Normalize the conflicts in path and write it out to * "thisimage" temporary file. */ - if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) { - ret = 1; - goto out; - } - - if (read_mmfile(&cur, rerere_path(id, "thisimage")) || - read_mmfile(&base, rerere_path(id, "preimage")) || - read_mmfile(&other, rerere_path(id, "postimage"))) { + if ((handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) || + read_mmfile(&cur, rerere_path(id, "thisimage"))) { ret = 1; goto out; } - /* - * A three-way merge. Note that this honors user-customizable - * low-level merge driver settings. - */ - ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL); + ret = try_merge(id, path, &cur, &result); if (ret) goto out; @@ -631,8 +701,6 @@ static int merge(const struct rerere_id *id, const char *path) out: free(cur.ptr); - free(base.ptr); - free(other.ptr); free(result.ptr); return ret; @@ -661,6 +729,13 @@ static void update_paths(struct string_list *update) rollback_lock_file(&index_lock); } +static void remove_variant(struct rerere_id *id) +{ + unlink_or_warn(rerere_path(id, "postimage")); + unlink_or_warn(rerere_path(id, "preimage")); + id->collection->status[id->variant] = 0; +} + /* * The path indicated by rr_item may still have conflict for which we * have a recorded resolution, in which case replay it and optionally @@ -672,12 +747,47 @@ static void do_rerere_one_path(struct string_list_item *rr_item, struct string_list *update) { const char *path = rr_item->string; - const struct rerere_id *id = rr_item->util; + struct rerere_id *id = rr_item->util; + struct rerere_dir *rr_dir = id->collection; + int variant; + + variant = id->variant; + + /* Has the user resolved it already? */ + if (variant >= 0) { + if (!handle_file(path, NULL, NULL)) { + copy_file(rerere_path(id, "postimage"), path, 0666); + id->collection->status[variant] |= RR_HAS_POSTIMAGE; + fprintf(stderr, "Recorded resolution for '%s'.\n", path); + free_rerere_id(rr_item); + rr_item->util = NULL; + return; + } + /* + * There may be other variants that can cleanly + * replay. Try them and update the variant number for + * this one. + */ + } + + /* Does any existing resolution apply cleanly? */ + for (variant = 0; variant < rr_dir->status_nr; variant++) { + const int both = RR_HAS_PREIMAGE | RR_HAS_POSTIMAGE; + struct rerere_id vid = *id; + + if ((rr_dir->status[variant] & both) != both) + continue; - /* Is there a recorded resolution we could attempt to apply? */ - if (has_rerere_resolution(id)) { - if (merge(id, path)) - return; /* failed to replay */ + vid.variant = variant; + if (merge(&vid, path)) + continue; /* failed to replay */ + + /* + * If there already is a different variant that applies + * cleanly, there is no point maintaining our own variant. + */ + if (0 <= id->variant && id->variant != variant) + remove_variant(id); if (rerere_autoupdate) string_list_insert(update, path); @@ -685,15 +795,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item, fprintf(stderr, "Resolved '%s' using previous resolution.\n", path); - } else if (!handle_file(path, NULL, NULL)) { - /* The user has resolved it. */ - copy_file(rerere_path(id, "postimage"), path, 0666); - fprintf(stderr, "Recorded resolution for '%s'.\n", path); - } else { + free_rerere_id(rr_item); + rr_item->util = NULL; return; } - free_rerere_id(rr_item); - rr_item->util = NULL; + + /* None of the existing one applies; we need a new variant */ + assign_variant(id); + + variant = id->variant; + handle_file(path, NULL, rerere_path(id, "preimage")); + if (id->collection->status[variant] & RR_HAS_POSTIMAGE) { + const char *path = rerere_path(id, "postimage"); + if (unlink(path)) + die_errno("cannot unlink stray '%s'", path); + id->collection->status[variant] &= ~RR_HAS_POSTIMAGE; + } + id->collection->status[variant] |= RR_HAS_PREIMAGE; + fprintf(stderr, "Recorded preimage for '%s'\n", path); } static int do_plain_rerere(struct string_list *rr, int fd) @@ -731,24 +850,8 @@ static int do_plain_rerere(struct string_list *rr, int fd) id = new_rerere_id(sha1); string_list_insert(rr, path)->util = id; - /* - * If the directory does not exist, create - * it. mkdir_in_gitdir() will fail with - * EEXIST if there already is one. - * - * NEEDSWORK: make sure "gc" does not remove - * preimage without removing the directory. - */ - if (mkdir_in_gitdir(rerere_path(id, NULL))) - continue; - - /* - * We are the first to encounter this - * conflict. Ask handle_file() to write the - * normalized contents to the "preimage" file. - */ - handle_file(path, NULL, rerere_path(id, "preimage")); - fprintf(stderr, "Recorded preimage for '%s'\n", path); + /* Ensure that the directory exists. */ + mkdir_in_gitdir(rerere_path(id, NULL)); } for (i = 0; i < rr->nr; i++) @@ -812,12 +915,111 @@ int setup_rerere(struct string_list *merge_rr, int flags) int rerere(int flags) { struct string_list merge_rr = STRING_LIST_INIT_DUP; - int fd; + int fd, status; fd = setup_rerere(&merge_rr, flags); if (fd < 0) return 0; - return do_plain_rerere(&merge_rr, fd); + status = do_plain_rerere(&merge_rr, fd); + free_rerere_dirs(); + return status; +} + +/* + * Subclass of rerere_io that reads from an in-core buffer that is a + * strbuf + */ +struct rerere_io_mem { + struct rerere_io io; + struct strbuf input; +}; + +/* + * ... and its getline() method implementation + */ +static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) +{ + struct rerere_io_mem *io = (struct rerere_io_mem *)io_; + char *ep; + size_t len; + + strbuf_release(sb); + if (!io->input.len) + return -1; + ep = memchr(io->input.buf, '\n', io->input.len); + if (!ep) + ep = io->input.buf + io->input.len; + else if (*ep == '\n') + ep++; + len = ep - io->input.buf; + strbuf_add(sb, io->input.buf, len); + strbuf_remove(&io->input, 0, len); + return 0; +} + +static int handle_cache(const char *path, unsigned char *sha1, const char *output) +{ + mmfile_t mmfile[3] = {{NULL}}; + mmbuffer_t result = {NULL, 0}; + const struct cache_entry *ce; + int pos, len, i, hunk_no; + struct rerere_io_mem io; + int marker_size = ll_merge_marker_size(path); + + /* + * Reproduce the conflicted merge in-core + */ + len = strlen(path); + pos = cache_name_pos(path, len); + if (0 <= pos) + return -1; + pos = -pos - 1; + + while (pos < active_nr) { + enum object_type type; + unsigned long size; + + ce = active_cache[pos++]; + if (ce_namelen(ce) != len || memcmp(ce->name, path, len)) + break; + i = ce_stage(ce) - 1; + if (!mmfile[i].ptr) { + mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[i].size = size; + } + } + for (i = 0; i < 3; i++) + if (!mmfile[i].ptr && !mmfile[i].size) + mmfile[i].ptr = xstrdup(""); + + /* + * NEEDSWORK: handle conflicts from merges with + * merge.renormalize set, too? + */ + ll_merge(&result, path, &mmfile[0], NULL, + &mmfile[1], "ours", + &mmfile[2], "theirs", NULL); + for (i = 0; i < 3; i++) + free(mmfile[i].ptr); + + memset(&io, 0, sizeof(io)); + io.io.getline = rerere_mem_getline; + if (output) + io.io.output = fopen(output, "w"); + else + io.io.output = NULL; + strbuf_init(&io.input, 0); + strbuf_attach(&io.input, result.ptr, result.size, result.size); + + /* + * Grab the conflict ID and optionally write the original + * contents with conflict markers out. + */ + hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size); + strbuf_release(&io.input); + if (io.io.output) + fclose(io.io.output); + return hunk_no; } static int rerere_forget_one_path(const char *path, struct string_list *rr) @@ -838,6 +1040,33 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr) /* Nuke the recorded resolution for the conflict */ id = new_rerere_id(sha1); + + for (id->variant = 0; + id->variant < id->collection->status_nr; + id->variant++) { + mmfile_t cur = { NULL, 0 }; + mmbuffer_t result = {NULL, 0}; + int cleanly_resolved; + + if (!has_rerere_resolution(id)) + continue; + + handle_cache(path, sha1, rerere_path(id, "thisimage")); + if (read_mmfile(&cur, rerere_path(id, "thisimage"))) { + free(cur.ptr); + return error("Failed to update conflicted state in '%s'", + path); + } + cleanly_resolved = !try_merge(id, path, &cur, &result); + free(result.ptr); + free(cur.ptr); + if (cleanly_resolved) + break; + } + + if (id->collection->status_nr <= id->variant) + return error("no remembered resolution for '%s'", path); + filename = rerere_path(id, "postimage"); if (unlink(filename)) return (errno == ENOENT @@ -897,29 +1126,16 @@ int rerere_forget(struct pathspec *pathspec) * Garbage collection support */ -/* - * Note that this is not reentrant but is used only one-at-a-time - * so it does not matter right now. - */ -static struct rerere_id *dirname_to_id(const char *name) -{ - static struct rerere_id id; - xsnprintf(id.hex, sizeof(id.hex), "%s", name); - return &id; -} - -static time_t rerere_created_at(const char *dir_name) +static time_t rerere_created_at(struct rerere_id *id) { struct stat st; - struct rerere_id *id = dirname_to_id(dir_name); return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } -static time_t rerere_last_used_at(const char *dir_name) +static time_t rerere_last_used_at(struct rerere_id *id) { struct stat st; - struct rerere_id *id = dirname_to_id(dir_name); return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime; } @@ -929,15 +1145,28 @@ static time_t rerere_last_used_at(const char *dir_name) */ static void unlink_rr_item(struct rerere_id *id) { - unlink(rerere_path(id, "thisimage")); - unlink(rerere_path(id, "preimage")); - unlink(rerere_path(id, "postimage")); - /* - * NEEDSWORK: what if this rmdir() fails? Wouldn't we then - * assume that we already have preimage recorded in - * do_plain_rerere()? - */ - rmdir(rerere_path(id, NULL)); + unlink_or_warn(rerere_path(id, "thisimage")); + remove_variant(id); + id->collection->status[id->variant] = 0; +} + +static void prune_one(struct rerere_id *id, time_t now, + int cutoff_resolve, int cutoff_noresolve) +{ + time_t then; + int cutoff; + + then = rerere_last_used_at(id); + if (then) + cutoff = cutoff_resolve; + else { + then = rerere_created_at(id); + if (!then) + return; + cutoff = cutoff_noresolve; + } + if (then < now - cutoff * 86400) + unlink_rr_item(id); } void rerere_gc(struct string_list *rr) @@ -945,8 +1174,8 @@ void rerere_gc(struct string_list *rr) struct string_list to_remove = STRING_LIST_INIT_DUP; DIR *dir; struct dirent *e; - int i, cutoff; - time_t now = time(NULL), then; + int i; + time_t now = time(NULL); int cutoff_noresolve = 15; int cutoff_resolve = 60; @@ -961,25 +1190,32 @@ void rerere_gc(struct string_list *rr) die_errno("unable to open rr-cache directory"); /* Collect stale conflict IDs ... */ while ((e = readdir(dir))) { + struct rerere_dir *rr_dir; + struct rerere_id id; + int now_empty; + if (is_dot_or_dotdot(e->d_name)) continue; - - then = rerere_last_used_at(e->d_name); - if (then) { - cutoff = cutoff_resolve; - } else { - then = rerere_created_at(e->d_name); - if (!then) - continue; - cutoff = cutoff_noresolve; + rr_dir = find_rerere_dir(e->d_name); + if (!rr_dir) + continue; /* or should we remove e->d_name? */ + + now_empty = 1; + for (id.variant = 0, id.collection = rr_dir; + id.variant < id.collection->status_nr; + id.variant++) { + prune_one(&id, now, cutoff_resolve, cutoff_noresolve); + if (id.collection->status[id.variant]) + now_empty = 0; } - if (then < now - cutoff * 86400) + if (now_empty) string_list_append(&to_remove, e->d_name); } closedir(dir); - /* ... and then remove them one-by-one */ + + /* ... and then remove the empty directories */ for (i = 0; i < to_remove.nr; i++) - unlink_rr_item(dirname_to_id(to_remove.items[i].string)); + rmdir(git_path("rr-cache/%s", to_remove.items[i].string)); string_list_clear(&to_remove, 0); rollback_lock_file(&write_lock); } @@ -1000,8 +1236,10 @@ void rerere_clear(struct string_list *merge_rr) for (i = 0; i < merge_rr->nr; i++) { struct rerere_id *id = merge_rr->items[i].util; - if (!has_rerere_resolution(id)) + if (!has_rerere_resolution(id)) { unlink_rr_item(id); + rmdir(rerere_path(id, NULL)); + } } unlink_or_warn(git_path_merge_rr()); rollback_lock_file(&write_lock); diff --git a/rerere.h b/rerere.h index 1222e91921..c2961feaaa 100644 --- a/rerere.h +++ b/rerere.h @@ -16,8 +16,10 @@ struct pathspec; */ extern void *RERERE_RESOLVED; +struct rerere_dir; struct rerere_id { - char hex[41]; + struct rerere_dir *collection; + int variant; }; extern int setup_rerere(struct string_list *, int); diff --git a/revision.c b/revision.c index 8b2dfe3160..b683476b9c 100644 --- a/revision.c +++ b/revision.c @@ -1356,8 +1356,10 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->skip_count = -1; revs->max_count = -1; revs->max_parents = -1; + revs->expand_tabs_in_log = -1; revs->commit_format = CMIT_FMT_DEFAULT; + revs->expand_tabs_in_log_default = 8; init_grep_defaults(); grep_init(&revs->grep_filter, prefix); @@ -1854,6 +1856,15 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; revs->pretty_given = 1; get_commit_format(arg+9, revs); + } else if (!strcmp(arg, "--expand-tabs")) { + revs->expand_tabs_in_log = 8; + } else if (!strcmp(arg, "--no-expand-tabs")) { + revs->expand_tabs_in_log = 0; + } else if (skip_prefix(arg, "--expand-tabs=", &arg)) { + int val; + if (strtol_i(arg, 10, &val) < 0 || val < 0) + die("'%s': not a non-negative integer", arg); + revs->expand_tabs_in_log = val; } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) { revs->show_notes = 1; revs->show_notes_given = 1; @@ -2327,6 +2338,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->first_parent_only && revs->bisect) die(_("--first-parent is incompatible with --bisect")); + if (revs->expand_tabs_in_log < 0) + revs->expand_tabs_in_log = revs->expand_tabs_in_log_default; + return left; } diff --git a/revision.h b/revision.h index dca0d38171..9fac1a607d 100644 --- a/revision.h +++ b/revision.h @@ -148,6 +148,8 @@ struct rev_info { linear:1; struct date_mode date_mode; + int expand_tabs_in_log; /* unset if negative */ + int expand_tabs_in_log_default; unsigned int abbrev; enum cmit_fmt commit_format; diff --git a/run-command.c b/run-command.c index c72601056c..e4593cd99b 100644 --- a/run-command.c +++ b/run-command.c @@ -590,6 +590,16 @@ static void *run_thread(void *data) struct async *async = data; intptr_t ret; + if (async->isolate_sigpipe) { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGPIPE); + if (pthread_sigmask(SIG_BLOCK, &mask, NULL) < 0) { + ret = error("unable to block SIGPIPE in async thread"); + return (void *)ret; + } + } + pthread_setspecific(async_key, async); ret = async->proc(async->proc_in, async->proc_out, async->data); return (void *)ret; @@ -902,7 +912,7 @@ struct parallel_processes { struct strbuf buffered_output; /* of finished children */ }; -static int default_start_failure(struct strbuf *err, +static int default_start_failure(struct strbuf *out, void *pp_cb, void *pp_task_cb) { @@ -910,7 +920,7 @@ static int default_start_failure(struct strbuf *err, } static int default_task_finished(int result, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void *pp_task_cb) { @@ -994,7 +1004,7 @@ static void pp_cleanup(struct parallel_processes *pp) * When get_next_task added messages to the buffer in its last * iteration, the buffered output is non empty. */ - fputs(pp->buffered_output.buf, stderr); + strbuf_write(&pp->buffered_output, stderr); strbuf_release(&pp->buffered_output); sigchain_pop_common(); @@ -1079,7 +1089,7 @@ static void pp_output(struct parallel_processes *pp) int i = pp->output_owner; if (pp->children[i].state == GIT_CP_WORKING && pp->children[i].err.len) { - fputs(pp->children[i].err.buf, stderr); + strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); } } @@ -1117,11 +1127,11 @@ static int pp_collect_finished(struct parallel_processes *pp) strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); strbuf_reset(&pp->children[i].err); } else { - fputs(pp->children[i].err.buf, stderr); + strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); /* Output all other finished child processes */ - fputs(pp->buffered_output.buf, stderr); + strbuf_write(&pp->buffered_output, stderr); strbuf_reset(&pp->buffered_output); /* diff --git a/run-command.h b/run-command.h index 3d1e59e26e..11f76b04ed 100644 --- a/run-command.h +++ b/run-command.h @@ -116,6 +116,7 @@ struct async { int proc_in; int proc_out; #endif + int isolate_sigpipe; }; int start_async(struct async *async); @@ -140,7 +141,7 @@ void NORETURN async_exit(int code); * return the negative signal number. */ typedef int (*get_next_task_fn)(struct child_process *cp, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void **pp_task_cb); @@ -149,7 +150,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp, * a new process. * * You must not write to stdout or stderr in this function. Add your - * message to the strbuf err instead, which will be printed without + * message to the strbuf out instead, which will be printed without * messing up the output of the other parallel processes. * * pp_cb is the callback cookie as passed into run_processes_parallel, @@ -159,7 +160,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp, * To send a signal to other child processes for abortion, return * the negative signal number. */ -typedef int (*start_failure_fn)(struct strbuf *err, +typedef int (*start_failure_fn)(struct strbuf *out, void *pp_cb, void *pp_task_cb); @@ -167,7 +168,7 @@ typedef int (*start_failure_fn)(struct strbuf *err, * This callback is called on every child process that finished processing. * * You must not write to stdout or stderr in this function. Add your - * message to the strbuf err instead, which will be printed without + * message to the strbuf out instead, which will be printed without * messing up the output of the other parallel processes. * * pp_cb is the callback cookie as passed into run_processes_parallel, @@ -178,7 +179,7 @@ typedef int (*start_failure_fn)(struct strbuf *err, * the negative signal number. */ typedef int (*task_finished_fn)(int result, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void *pp_task_cb); diff --git a/send-pack.c b/send-pack.c index 047bd18fde..37ee04ea3b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -518,6 +518,7 @@ int send_pack(struct send_pack_args *args, demux.proc = sideband_demux; demux.data = fd; demux.out = -1; + demux.isolate_sigpipe = 1; if (start_async(&demux)) die("send-pack: unable to fork off sideband demultiplexer"); in = demux.out; @@ -531,8 +532,10 @@ int send_pack(struct send_pack_args *args, close(out); if (git_connection_is_socket(conn)) shutdown(fd[0], SHUT_WR); - if (use_sideband) + if (use_sideband) { + close(demux.out); finish_async(&demux); + } fd[1] = -1; return -1; } @@ -551,11 +554,11 @@ int send_pack(struct send_pack_args *args, packet_flush(out); if (use_sideband && cmds_sent) { + close(demux.out); if (finish_async(&demux)) { error("error in sideband demultiplexer"); ret = -1; } - close(demux.out); } if (ret < 0) diff --git a/setup.c b/setup.c index 3439ec6d81..c86bf5c9fa 100644 --- a/setup.c +++ b/setup.c @@ -5,7 +5,6 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; static int work_tree_config_is_bogus; -static struct string_list unknown_extensions = STRING_LIST_INIT_DUP; static struct startup_info the_startup_info; struct startup_info *startup_info = &the_startup_info; @@ -103,7 +102,7 @@ char *prefix_path_gently(const char *prefix, int len, return NULL; } } else { - sanitized = xstrfmt("%.*s%s", len, prefix, path); + sanitized = xstrfmt("%.*s%s", len, len ? prefix : "", path); if (remaining_prefix) *remaining_prefix = len; if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) { @@ -373,14 +372,13 @@ void setup_work_tree(void) initialized = 1; } -static int check_repo_format(const char *var, const char *value, void *cb) +static int check_repo_format(const char *var, const char *value, void *vdata) { + struct repository_format *data = vdata; const char *ext; if (strcmp(var, "core.repositoryformatversion") == 0) - repository_format_version = git_config_int(var, value); - else if (strcmp(var, "core.sharedrepository") == 0) - shared_repository = git_config_perm(var, value); + data->version = git_config_int(var, value); else if (skip_prefix(var, "extensions.", &ext)) { /* * record any known extensions here; otherwise, @@ -390,9 +388,15 @@ static int check_repo_format(const char *var, const char *value, void *cb) if (!strcmp(ext, "noop")) ; else if (!strcmp(ext, "preciousobjects")) - repository_format_precious_objects = git_config_bool(var, value); + data->precious_objects = git_config_bool(var, value); else - string_list_append(&unknown_extensions, ext); + string_list_append(&data->unknown_extensions, ext); + } else if (strcmp(var, "core.bare") == 0) { + data->is_bare = git_config_bool(var, value); + } else if (strcmp(var, "core.worktree") == 0) { + if (!value) + return config_error_nonbool(var); + data->work_tree = xstrdup(value); } return 0; } @@ -400,56 +404,84 @@ static int check_repo_format(const char *var, const char *value, void *cb) static int check_repository_format_gently(const char *gitdir, int *nongit_ok) { struct strbuf sb = STRBUF_INIT; - const char *repo_config; - config_fn_t fn; - int ret = 0; - - string_list_clear(&unknown_extensions, 0); + struct strbuf err = STRBUF_INIT; + struct repository_format candidate; + int has_common; - if (get_common_dir(&sb, gitdir)) - fn = check_repo_format; - else - fn = check_repository_format_version; + has_common = get_common_dir(&sb, gitdir); strbuf_addstr(&sb, "/config"); - repo_config = sb.buf; + read_repository_format(&candidate, sb.buf); + strbuf_release(&sb); /* - * git_config() can't be used here because it calls git_pathdup() - * to get $GIT_CONFIG/config. That call will make setup_git_env() - * set git_dir to ".git". - * - * We are in gitdir setup, no git dir has been found useable yet. - * Use a gentler version of git_config() to check if this repo - * is a good one. + * For historical use of check_repository_format() in git-init, + * we treat a missing config as a silent "ok", even when nongit_ok + * is unset. */ - git_config_early(fn, NULL, repo_config); - if (GIT_REPO_VERSION_READ < repository_format_version) { - if (!nongit_ok) - die ("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION_READ, repository_format_version); - warning("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION_READ, repository_format_version); - warning("Please upgrade Git"); - *nongit_ok = -1; - ret = -1; + if (candidate.version < 0) + return 0; + + if (verify_repository_format(&candidate, &err) < 0) { + if (nongit_ok) { + warning("%s", err.buf); + strbuf_release(&err); + *nongit_ok = -1; + return -1; + } + die("%s", err.buf); } - if (repository_format_version >= 1 && unknown_extensions.nr) { + repository_format_precious_objects = candidate.precious_objects; + string_list_clear(&candidate.unknown_extensions, 0); + if (!has_common) { + if (candidate.is_bare != -1) { + is_bare_repository_cfg = candidate.is_bare; + if (is_bare_repository_cfg == 1) + inside_work_tree = -1; + } + if (candidate.work_tree) { + free(git_work_tree_cfg); + git_work_tree_cfg = candidate.work_tree; + inside_work_tree = -1; + } + } else { + free(candidate.work_tree); + } + + return 0; +} + +int read_repository_format(struct repository_format *format, const char *path) +{ + memset(format, 0, sizeof(*format)); + format->version = -1; + format->is_bare = -1; + string_list_init(&format->unknown_extensions, 1); + git_config_from_file(check_repo_format, path, format); + return format->version; +} + +int verify_repository_format(const struct repository_format *format, + struct strbuf *err) +{ + if (GIT_REPO_VERSION_READ < format->version) { + strbuf_addf(err, _("Expected git repo version <= %d, found %d"), + GIT_REPO_VERSION_READ, format->version); + return -1; + } + + if (format->version >= 1 && format->unknown_extensions.nr) { int i; - if (!nongit_ok) - die("unknown repository extension: %s", - unknown_extensions.items[0].string); + strbuf_addstr(err, _("unknown repository extensions found:")); - for (i = 0; i < unknown_extensions.nr; i++) - warning("unknown repository extension: %s", - unknown_extensions.items[i].string); - *nongit_ok = -1; - ret = -1; + for (i = 0; i < format->unknown_extensions.nr; i++) + strbuf_addf(err, "\n\t%s", + format->unknown_extensions.items[i].string); + return -1; } - strbuf_release(&sb); - return ret; + return 0; } /* @@ -965,30 +997,10 @@ int git_config_perm(const char *var, const char *value) return -(i & 0666); } -int check_repository_format_version(const char *var, const char *value, void *cb) -{ - int ret = check_repo_format(var, value, cb); - if (ret) - return ret; - if (strcmp(var, "core.bare") == 0) { - is_bare_repository_cfg = git_config_bool(var, value); - if (is_bare_repository_cfg == 1) - inside_work_tree = -1; - } else if (strcmp(var, "core.worktree") == 0) { - if (!value) - return config_error_nonbool(var); - free(git_work_tree_cfg); - git_work_tree_cfg = xstrdup(value); - inside_work_tree = -1; - } - return 0; -} - -int check_repository_format(void) +void check_repository_format(void) { check_repository_format_gently(get_git_dir(), NULL); startup_info->have_repository = 1; - return 0; } /* diff --git a/strbuf.c b/strbuf.c index 2c08dbb153..1ba600bd78 100644 --- a/strbuf.c +++ b/strbuf.c @@ -395,6 +395,12 @@ ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint) return cnt; } +ssize_t strbuf_write(struct strbuf *sb, FILE *f) +{ + return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0; +} + + #define STRBUF_MAXLINK (2*PATH_MAX) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) diff --git a/strbuf.h b/strbuf.h index f72fd14c2e..7987405313 100644 --- a/strbuf.h +++ b/strbuf.h @@ -386,6 +386,12 @@ extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint */ extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); +/** + * Write the whole content of the strbuf to the stream not stopping at + * NUL bytes. + */ +extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream); + /** * Read a line from a FILE *, overwriting the existing contents of * the strbuf. The strbuf_getline*() family of functions share diff --git a/string-list.c b/string-list.c index 2a32a3f1f5..62d20846cb 100644 --- a/string-list.c +++ b/string-list.c @@ -231,12 +231,12 @@ void string_list_sort(struct string_list *list) struct string_list_item *unsorted_string_list_lookup(struct string_list *list, const char *string) { - int i; + struct string_list_item *item; compare_strings_fn cmp = list->cmp ? list->cmp : strcmp; - for (i = 0; i < list->nr; i++) - if (!cmp(string, list->items[i].string)) - return list->items + i; + for_each_string_list_item(item, list) + if (!cmp(string, item->string)) + return item; return NULL; } diff --git a/submodule-config.c b/submodule-config.c index 92502b594d..8ac5031ade 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -59,6 +59,7 @@ static void free_one_config(struct submodule_entry *entry) { free((void *) entry->config->path); free((void *) entry->config->name); + free((void *) entry->config->update_strategy.command); free(entry->config); } @@ -194,6 +195,8 @@ static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, submodule->path = NULL; submodule->url = NULL; + submodule->update_strategy.type = SM_UPDATE_UNSPECIFIED; + submodule->update_strategy.command = NULL; submodule->fetch_recurse = RECURSE_SUBMODULES_NONE; submodule->ignore = NULL; @@ -293,7 +296,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!strcmp(item.buf, "path")) { if (!value) ret = config_error_nonbool(var); - else if (!me->overwrite && submodule->path != NULL) + else if (!me->overwrite && submodule->path) warn_multiple_config(me->commit_sha1, submodule->name, "path"); else { @@ -317,7 +320,7 @@ static int parse_config(const char *var, const char *value, void *data) } else if (!strcmp(item.buf, "ignore")) { if (!value) ret = config_error_nonbool(var); - else if (!me->overwrite && submodule->ignore != NULL) + else if (!me->overwrite && submodule->ignore) warn_multiple_config(me->commit_sha1, submodule->name, "ignore"); else if (strcmp(value, "untracked") && @@ -333,13 +336,23 @@ static int parse_config(const char *var, const char *value, void *data) } else if (!strcmp(item.buf, "url")) { if (!value) { ret = config_error_nonbool(var); - } else if (!me->overwrite && submodule->url != NULL) { + } else if (!me->overwrite && submodule->url) { warn_multiple_config(me->commit_sha1, submodule->name, "url"); } else { free((void *) submodule->url); submodule->url = xstrdup(value); } + } else if (!strcmp(item.buf, "update")) { + if (!value) + ret = config_error_nonbool(var); + else if (!me->overwrite && + submodule->update_strategy.type != SM_UPDATE_UNSPECIFIED) + warn_multiple_config(me->commit_sha1, submodule->name, + "update"); + else if (parse_submodule_update_strategy(value, + &submodule->update_strategy) < 0) + die(_("invalid value for %s"), var); } strbuf_release(&name); @@ -392,8 +405,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, struct hashmap_iter iter; struct submodule_entry *entry; - hashmap_iter_init(&cache->for_name, &iter); - entry = hashmap_iter_next(&iter); + entry = hashmap_iter_first(&cache->for_name, &iter); if (!entry) return NULL; return entry->config; diff --git a/submodule-config.h b/submodule-config.h index 9bfa65af03..e4857f53a8 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -2,6 +2,7 @@ #define SUBMODULE_CONFIG_CACHE_H #include "hashmap.h" +#include "submodule.h" #include "strbuf.h" /* @@ -14,6 +15,7 @@ struct submodule { const char *url; int fetch_recurse; const char *ignore; + struct submodule_update_strategy update_strategy; /* the sha1 blob id of the responsible .gitmodules file */ unsigned char gitmodules_sha1[20]; }; diff --git a/submodule.c b/submodule.c index 62c4356c50..90825e17fa 100644 --- a/submodule.c +++ b/submodule.c @@ -15,6 +15,7 @@ #include "thread-utils.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; +static int parallel_jobs = 1; static struct string_list changed_submodule_paths; static int initialized_fetch_ref_tips; static struct sha1_array ref_tips_before_fetch; @@ -169,7 +170,12 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, int submodule_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "submodule.")) + if (!strcmp(var, "submodule.fetchjobs")) { + parallel_jobs = git_config_int(var, value); + if (parallel_jobs < 0) + die(_("negative values not allowed for submodule.fetchJobs")); + return 0; + } else if (starts_with(var, "submodule.")) return parse_submodule_config_option(var, value); else if (!strcmp(var, "fetch.recursesubmodules")) { config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); @@ -210,6 +216,27 @@ void gitmodules_config(void) } } +int parse_submodule_update_strategy(const char *value, + struct submodule_update_strategy *dst) +{ + free((void*)dst->command); + dst->command = NULL; + if (!strcmp(value, "none")) + dst->type = SM_UPDATE_NONE; + else if (!strcmp(value, "checkout")) + dst->type = SM_UPDATE_CHECKOUT; + else if (!strcmp(value, "rebase")) + dst->type = SM_UPDATE_REBASE; + else if (!strcmp(value, "merge")) + dst->type = SM_UPDATE_MERGE; + else if (skip_prefix(value, "!", &value)) { + dst->type = SM_UPDATE_COMMAND; + dst->command = xstrdup(value); + } else + return -1; + return 0; +} + void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -750,6 +777,9 @@ int fetch_populated_submodules(const struct argv_array *options, argv_array_push(&spf.args, "--recurse-submodules-default"); /* default value, "--submodule-prefix" and its value are added later */ + if (max_parallel_jobs < 0) + max_parallel_jobs = parallel_jobs; + calculate_changed_submodule_paths(); run_processes_parallel(max_parallel_jobs, get_next_submodule, @@ -1094,3 +1124,8 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) strbuf_release(&rel_path); free((void *)real_work_tree); } + +int parallel_submodules(void) +{ + return parallel_jobs; +} diff --git a/submodule.h b/submodule.h index e06eaa5ebb..7ef3775184 100644 --- a/submodule.h +++ b/submodule.h @@ -14,6 +14,21 @@ enum { RECURSE_SUBMODULES_ON = 2 }; +enum submodule_update_type { + SM_UPDATE_UNSPECIFIED = 0, + SM_UPDATE_CHECKOUT, + SM_UPDATE_REBASE, + SM_UPDATE_MERGE, + SM_UPDATE_NONE, + SM_UPDATE_COMMAND +}; + +struct submodule_update_strategy { + enum submodule_update_type type; + const char *command; +}; +#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} + int is_staging_gitmodules_ok(void); int update_path_in_gitmodules(const char *oldpath, const char *newpath); int remove_path_from_gitmodules(const char *path); @@ -22,6 +37,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); 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); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, @@ -42,5 +59,6 @@ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam struct string_list *needs_pushing); int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +int parallel_submodules(void); #endif diff --git a/t/helper/.gitignore b/t/helper/.gitignore new file mode 100644 index 0000000000..d6e8b36798 --- /dev/null +++ b/t/helper/.gitignore @@ -0,0 +1,33 @@ +/test-chmtime +/test-ctype +/test-config +/test-date +/test-delta +/test-dump-cache-tree +/test-dump-split-index +/test-dump-untracked-cache +/test-fake-ssh +/test-scrap-cache-tree +/test-genrandom +/test-hashmap +/test-index-version +/test-line-buffer +/test-match-trees +/test-mergesort +/test-mktemp +/test-parse-options +/test-path-utils +/test-prio-queue +/test-read-cache +/test-regex +/test-revision-walking +/test-run-command +/test-sha1 +/test-sha1-array +/test-sigchain +/test-string-list +/test-submodule-config +/test-subprocess +/test-svn-fe +/test-urlmatch-normalization +/test-wildmatch diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c new file mode 100644 index 0000000000..dfe8a83261 --- /dev/null +++ b/t/helper/test-chmtime.c @@ -0,0 +1,119 @@ +/* + * This program can either change modification time of the given + * file(s) or just print it. The program does not change atime or + * ctime (their values are explicitly preserved). + * + * The mtime can be changed to an absolute value: + * + * test-chmtime = file... + * + * Relative to the current time as returned by time(3): + * + * test-chmtime =+ (or =-) file... + * + * Or relative to the current mtime of the file: + * + * test-chmtime file... + * test-chmtime + (or -) file... + * + * Examples: + * + * To just print the mtime use --verbose and set the file mtime offset to 0: + * + * test-chmtime -v +0 file + * + * To set the mtime to current time: + * + * test-chmtime =+0 file + * + */ +#include "git-compat-util.h" +#include + +static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-) ..."; + +static int timespec_arg(const char *arg, long int *set_time, int *set_eq) +{ + char *test; + const char *timespec = arg; + *set_eq = (*timespec == '=') ? 1 : 0; + if (*set_eq) { + timespec++; + if (*timespec == '+') { + *set_eq = 2; /* relative "in the future" */ + timespec++; + } + } + *set_time = strtol(timespec, &test, 10); + if (*test) { + fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1); + return 0; + } + if ((*set_eq && *set_time < 0) || *set_eq == 2) { + time_t now = time(NULL); + *set_time += now; + } + return 1; +} + +int main(int argc, char *argv[]) +{ + static int verbose; + + int i = 1; + /* no mtime change by default */ + int set_eq = 0; + long int set_time = 0; + + if (argc < 3) + goto usage; + + if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + verbose = 1; + ++i; + } + if (timespec_arg(argv[i], &set_time, &set_eq)) + ++i; + else + goto usage; + + for (; i < argc; i++) { + struct stat sb; + struct utimbuf utb; + + if (stat(argv[i], &sb) < 0) { + fprintf(stderr, "Failed to stat %s: %s\n", + argv[i], strerror(errno)); + return 1; + } + +#ifdef GIT_WINDOWS_NATIVE + if (!(sb.st_mode & S_IWUSR) && + chmod(argv[i], sb.st_mode | S_IWUSR)) { + fprintf(stderr, "Could not make user-writable %s: %s", + argv[i], strerror(errno)); + return 1; + } +#endif + + utb.actime = sb.st_atime; + utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; + + if (verbose) { + uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime; + printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); + } + + if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) { + fprintf(stderr, "Failed to modify time on %s: %s\n", + argv[i], strerror(errno)); + return 1; + } + } + + return 0; + +usage: + fprintf(stderr, "usage: %s %s\n", argv[0], usage_str); + return 1; +} diff --git a/t/helper/test-config.c b/t/helper/test-config.c new file mode 100644 index 0000000000..6a77552210 --- /dev/null +++ b/t/helper/test-config.c @@ -0,0 +1,152 @@ +#include "cache.h" +#include "string-list.h" + +/* + * This program exposes the C API of the configuration mechanism + * as a set of simple commands in order to facilitate testing. + * + * Reads stdin and prints result of command to stdout: + * + * get_value -> prints the value with highest priority for the entered key + * + * get_value_multi -> prints all values for the entered key in increasing order + * of priority + * + * get_int -> print integer value for the entered key or die + * + * get_bool -> print bool value for the entered key or die + * + * get_string -> print string value for the entered key or die + * + * configset_get_value -> returns value with the highest priority for the entered key + * from a config_set constructed from files entered as arguments. + * + * configset_get_value_multi -> returns value_list for the entered key sorted in + * ascending order of priority from a config_set + * constructed from files entered as arguments. + * + * Examples: + * + * To print the value with highest priority for key "foo.bAr Baz.rock": + * test-config get_value "foo.bAr Baz.rock" + * + */ + + +int main(int argc, char **argv) +{ + int i, val; + const char *v; + const struct string_list *strptr; + struct config_set cs; + git_configset_init(&cs); + + if (argc < 2) { + fprintf(stderr, "Please, provide a command name on the command-line\n"); + goto exit1; + } else if (argc == 3 && !strcmp(argv[1], "get_value")) { + if (!git_config_get_value(argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { + strptr = git_config_get_value_multi(argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_int")) { + if (!git_config_get_int(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_bool")) { + if (!git_config_get_bool(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_string")) { + if (!git_config_get_string_const(argv[2], &v)) { + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + if (!git_configset_get_value(&cs, argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value_multi")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + strptr = git_configset_get_value_multi(&cs, argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } + + die("%s: Please check the syntax and the function name", argv[0]); + +exit0: + git_configset_clear(&cs); + return 0; + +exit1: + git_configset_clear(&cs); + return 1; + +exit2: + git_configset_clear(&cs); + return 2; +} diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c new file mode 100644 index 0000000000..707a821f03 --- /dev/null +++ b/t/helper/test-ctype.c @@ -0,0 +1,42 @@ +#include "cache.h" + +static int rc; + +static void report_error(const char *class, int ch) +{ + printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch); + rc = 1; +} + +static int is_in(const char *s, int ch) +{ + /* We can't find NUL using strchr. It's classless anyway. */ + if (ch == '\0') + return 0; + return !!strchr(s, ch); +} + +#define TEST_CLASS(t,s) { \ + int i; \ + for (i = 0; i < 256; i++) { \ + if (is_in(s, i) != t(i)) \ + report_error(#t, i); \ + } \ +} + +#define DIGIT "0123456789" +#define LOWER "abcdefghijklmnopqrstuvwxyz" +#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +int main(int argc, char **argv) +{ + TEST_CLASS(isdigit, DIGIT); + TEST_CLASS(isspace, " \n\r\t"); + TEST_CLASS(isalpha, LOWER UPPER); + TEST_CLASS(isalnum, LOWER UPPER DIGIT); + TEST_CLASS(is_glob_special, "*?[\\"); + TEST_CLASS(is_regex_special, "$()*+.?[\\^{|"); + TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); + + return rc; +} diff --git a/t/helper/test-date.c b/t/helper/test-date.c new file mode 100644 index 0000000000..63f373557e --- /dev/null +++ b/t/helper/test-date.c @@ -0,0 +1,73 @@ +#include "cache.h" + +static const char *usage_msg = "\n" +" test-date show [time_t]...\n" +" test-date parse [date]...\n" +" test-date approxidate [date]...\n"; + +static void show_dates(char **argv, struct timeval *now) +{ + struct strbuf buf = STRBUF_INIT; + + for (; *argv; argv++) { + time_t t = atoi(*argv); + show_date_relative(t, 0, now, &buf); + printf("%s -> %s\n", *argv, buf.buf); + } + strbuf_release(&buf); +} + +static void parse_dates(char **argv, struct timeval *now) +{ + struct strbuf result = STRBUF_INIT; + + for (; *argv; argv++) { + unsigned long t; + int tz; + + strbuf_reset(&result); + parse_date(*argv, &result); + if (sscanf(result.buf, "%lu %d", &t, &tz) == 2) + printf("%s -> %s\n", + *argv, show_date(t, tz, DATE_MODE(ISO8601))); + else + printf("%s -> bad\n", *argv); + } + strbuf_release(&result); +} + +static void parse_approxidate(char **argv, struct timeval *now) +{ + for (; *argv; argv++) { + time_t t; + t = approxidate_relative(*argv, now); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601))); + } +} + +int main(int argc, char **argv) +{ + struct timeval now; + const char *x; + + x = getenv("TEST_DATE_NOW"); + if (x) { + now.tv_sec = atoi(x); + now.tv_usec = 0; + } + else + gettimeofday(&now, NULL); + + argv++; + if (!*argv) + usage(usage_msg); + if (!strcmp(*argv, "show")) + show_dates(argv+1, &now); + else if (!strcmp(*argv, "parse")) + parse_dates(argv+1, &now); + else if (!strcmp(*argv, "approxidate")) + parse_approxidate(argv+1, &now); + else + usage(usage_msg); + return 0; +} diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c new file mode 100644 index 0000000000..4595cd6433 --- /dev/null +++ b/t/helper/test-delta.c @@ -0,0 +1,78 @@ +/* + * test-delta.c: test code to exercise diff-delta.c and patch-delta.c + * + * (C) 2005 Nicolas Pitre + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "git-compat-util.h" +#include "delta.h" +#include "cache.h" + +static const char usage_str[] = + "test-delta (-d|-p) "; + +int main(int argc, char *argv[]) +{ + int fd; + struct stat st; + void *from_buf, *data_buf, *out_buf; + unsigned long from_size, data_size, out_size; + + if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) { + fprintf(stderr, "usage: %s\n", usage_str); + return 1; + } + + fd = open(argv[2], O_RDONLY); + if (fd < 0 || fstat(fd, &st)) { + perror(argv[2]); + return 1; + } + from_size = st.st_size; + from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (from_buf == MAP_FAILED) { + perror(argv[2]); + close(fd); + return 1; + } + close(fd); + + fd = open(argv[3], O_RDONLY); + if (fd < 0 || fstat(fd, &st)) { + perror(argv[3]); + return 1; + } + data_size = st.st_size; + data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data_buf == MAP_FAILED) { + perror(argv[3]); + close(fd); + return 1; + } + close(fd); + + if (argv[1][1] == 'd') + out_buf = diff_delta(from_buf, from_size, + data_buf, data_size, + &out_size, 0); + else + out_buf = patch_delta(from_buf, from_size, + data_buf, data_size, + &out_size); + if (!out_buf) { + fprintf(stderr, "delta operation failed (returned NULL)\n"); + return 1; + } + + fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) { + perror(argv[4]); + return 1; + } + + return 0; +} diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c new file mode 100644 index 0000000000..bb53c0aa65 --- /dev/null +++ b/t/helper/test-dump-cache-tree.c @@ -0,0 +1,67 @@ +#include "cache.h" +#include "tree.h" +#include "cache-tree.h" + + +static void dump_one(struct cache_tree *it, const char *pfx, const char *x) +{ + if (it->entry_count < 0) + printf("%-40s %s%s (%d subtrees)\n", + "invalid", x, pfx, it->subtree_nr); + else + printf("%s %s%s (%d entries, %d subtrees)\n", + sha1_to_hex(it->sha1), x, pfx, + it->entry_count, it->subtree_nr); +} + +static int dump_cache_tree(struct cache_tree *it, + struct cache_tree *ref, + const char *pfx) +{ + int i; + int errs = 0; + + if (!it || !ref) + /* missing in either */ + return 0; + + if (it->entry_count < 0) { + /* invalid */ + dump_one(it, pfx, ""); + dump_one(ref, pfx, "#(ref) "); + } + else { + dump_one(it, pfx, ""); + if (hashcmp(it->sha1, ref->sha1) || + ref->entry_count != it->entry_count || + ref->subtree_nr != it->subtree_nr) { + /* claims to be valid but is lying */ + dump_one(ref, pfx, "#(ref) "); + errs = 1; + } + } + + for (i = 0; i < it->subtree_nr; i++) { + char path[PATH_MAX]; + struct cache_tree_sub *down = it->down[i]; + struct cache_tree_sub *rdwn; + + rdwn = cache_tree_sub(ref, down->name); + xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name); + if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path)) + errs = 1; + } + return errs; +} + +int main(int ac, char **av) +{ + struct index_state istate; + struct cache_tree *another = cache_tree(); + if (read_cache() < 0) + die("unable to read index file"); + istate = the_index; + istate.cache_tree = another; + cache_tree_update(&istate, WRITE_TREE_DRY_RUN); + return dump_cache_tree(active_cache_tree, another, ""); +} diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c new file mode 100644 index 0000000000..861d28c9b6 --- /dev/null +++ b/t/helper/test-dump-split-index.c @@ -0,0 +1,36 @@ +#include "cache.h" +#include "split-index.h" +#include "ewah/ewok.h" + +static void show_bit(size_t pos, void *data) +{ + printf(" %d", (int)pos); +} + +int main(int ac, char **av) +{ + struct split_index *si; + int i; + + do_read_index(&the_index, av[1], 1); + printf("own %s\n", sha1_to_hex(the_index.sha1)); + si = the_index.split_index; + if (!si) { + printf("not a split index\n"); + return 0; + } + printf("base %s\n", sha1_to_hex(si->base_sha1)); + for (i = 0; i < the_index.cache_nr; i++) { + struct cache_entry *ce = the_index.cache[i]; + printf("%06o %s %d\t%s\n", ce->ce_mode, + sha1_to_hex(ce->sha1), ce_stage(ce), ce->name); + } + printf("replacements:"); + if (si->replace_bitmap) + ewah_each_bit(si->replace_bitmap, show_bit, NULL); + printf("\ndeletions:"); + if (si->delete_bitmap) + ewah_each_bit(si->delete_bitmap, show_bit, NULL); + printf("\n"); + return 0; +} diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c new file mode 100644 index 0000000000..0a1c285246 --- /dev/null +++ b/t/helper/test-dump-untracked-cache.c @@ -0,0 +1,66 @@ +#include "cache.h" +#include "dir.h" + +static int compare_untracked(const void *a_, const void *b_) +{ + const char *const *a = a_; + const char *const *b = b_; + return strcmp(*a, *b); +} + +static int compare_dir(const void *a_, const void *b_) +{ + const struct untracked_cache_dir *const *a = a_; + const struct untracked_cache_dir *const *b = b_; + return strcmp((*a)->name, (*b)->name); +} + +static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) +{ + int i, len; + qsort(ucd->untracked, ucd->untracked_nr, sizeof(*ucd->untracked), + compare_untracked); + qsort(ucd->dirs, ucd->dirs_nr, sizeof(*ucd->dirs), + compare_dir); + len = base->len; + strbuf_addf(base, "%s/", ucd->name); + printf("%s %s", base->buf, + sha1_to_hex(ucd->exclude_sha1)); + if (ucd->recurse) + fputs(" recurse", stdout); + if (ucd->check_only) + fputs(" check_only", stdout); + if (ucd->valid) + fputs(" valid", stdout); + printf("\n"); + for (i = 0; i < ucd->untracked_nr; i++) + printf("%s\n", ucd->untracked[i]); + for (i = 0; i < ucd->dirs_nr; i++) + dump(ucd->dirs[i], base); + strbuf_setlen(base, len); +} + +int main(int ac, char **av) +{ + struct untracked_cache *uc; + struct strbuf base = STRBUF_INIT; + + /* Hack to avoid modifying the untracked cache when we read it */ + ignore_untracked_cache_config = 1; + + setup_git_directory(); + if (read_cache() < 0) + die("unable to read index file"); + uc = the_index.untracked; + if (!uc) { + printf("no untracked cache\n"); + return 0; + } + printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1)); + printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1)); + printf("exclude_per_dir %s\n", uc->exclude_per_dir); + printf("flags %08x\n", uc->dir_flags); + if (uc->root) + dump(uc->root, &base); + return 0; +} diff --git a/t/helper/test-fake-ssh.c b/t/helper/test-fake-ssh.c new file mode 100644 index 0000000000..980de216e1 --- /dev/null +++ b/t/helper/test-fake-ssh.c @@ -0,0 +1,30 @@ +#include "git-compat-util.h" +#include "run-command.h" +#include "strbuf.h" + +int main(int argc, char **argv) +{ + const char *trash_directory = getenv("TRASH_DIRECTORY"); + struct strbuf buf = STRBUF_INIT; + FILE *f; + int i; + const char *child_argv[] = { NULL, NULL }; + + /* First, print all parameters into $TRASH_DIRECTORY/ssh-output */ + if (!trash_directory) + die("Need a TRASH_DIRECTORY!"); + strbuf_addf(&buf, "%s/ssh-output", trash_directory); + f = fopen(buf.buf, "w"); + if (!f) + die("Could not write to %s", buf.buf); + for (i = 0; i < argc; i++) + fprintf(f, "%s%s", i > 0 ? " " : "", i > 0 ? argv[i] : "ssh:"); + fprintf(f, "\n"); + fclose(f); + + /* Now, evaluate the *last* parameter */ + if (argc < 2) + return 0; + child_argv[0] = argv[argc - 1]; + return run_command_v_opt(child_argv, RUN_USING_SHELL); +} diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c new file mode 100644 index 0000000000..54824d0754 --- /dev/null +++ b/t/helper/test-genrandom.c @@ -0,0 +1,33 @@ +/* + * Simple random data generator used to create reproducible test files. + * This is inspired from POSIX.1-2001 implementation example for rand(). + * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. + */ + +#include "git-compat-util.h" + +int main(int argc, char *argv[]) +{ + unsigned long count, next = 0; + unsigned char *c; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: %s []\n", argv[0]); + return 1; + } + + c = (unsigned char *) argv[1]; + do { + next = next * 11 + *c; + } while (*c++); + + count = (argc == 3) ? strtoul(argv[2], NULL, 0) : -1L; + + while (count--) { + next = next * 1103515245 + 12345; + if (putchar((next >> 16) & 0xff) == EOF) + return -1; + } + + return 0; +} diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c new file mode 100644 index 0000000000..cc2891dd97 --- /dev/null +++ b/t/helper/test-hashmap.c @@ -0,0 +1,264 @@ +#include "git-compat-util.h" +#include "hashmap.h" + +struct test_entry +{ + struct hashmap_entry ent; + /* key and value as two \0-terminated strings */ + char key[FLEX_ARRAY]; +}; + +static const char *get_value(const struct test_entry *e) +{ + return e->key + strlen(e->key) + 1; +} + +static int test_entry_cmp(const struct test_entry *e1, + const struct test_entry *e2, const char* key) +{ + return strcmp(e1->key, key ? key : e2->key); +} + +static int test_entry_cmp_icase(const struct test_entry *e1, + const struct test_entry *e2, const char* key) +{ + return strcasecmp(e1->key, key ? key : e2->key); +} + +static struct test_entry *alloc_test_entry(int hash, char *key, int klen, + char *value, int vlen) +{ + struct test_entry *entry = malloc(sizeof(struct test_entry) + klen + + vlen + 2); + hashmap_entry_init(entry, hash); + memcpy(entry->key, key, klen + 1); + memcpy(entry->key + klen + 1, value, vlen + 1); + return entry; +} + +#define HASH_METHOD_FNV 0 +#define HASH_METHOD_I 1 +#define HASH_METHOD_IDIV10 2 +#define HASH_METHOD_0 3 +#define HASH_METHOD_X2 4 +#define TEST_SPARSE 8 +#define TEST_ADD 16 +#define TEST_SIZE 100000 + +static unsigned int hash(unsigned int method, unsigned int i, const char *key) +{ + unsigned int hash = 0; + switch (method & 3) + { + case HASH_METHOD_FNV: + hash = strhash(key); + break; + case HASH_METHOD_I: + hash = i; + break; + case HASH_METHOD_IDIV10: + hash = i / 10; + break; + case HASH_METHOD_0: + hash = 0; + break; + } + + if (method & HASH_METHOD_X2) + hash = 2 * hash; + return hash; +} + +/* + * Test performance of hashmap.[ch] + * Usage: time echo "perfhashmap method rounds" | test-hashmap + */ +static void perf_hashmap(unsigned int method, unsigned int rounds) +{ + struct hashmap map; + char buf[16]; + struct test_entry **entries; + unsigned int *hashes; + unsigned int i, j; + + entries = malloc(TEST_SIZE * sizeof(struct test_entry *)); + hashes = malloc(TEST_SIZE * sizeof(int)); + for (i = 0; i < TEST_SIZE; i++) { + snprintf(buf, sizeof(buf), "%i", i); + entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0); + hashes[i] = hash(method, i, entries[i]->key); + } + + if (method & TEST_ADD) { + /* test adding to the map */ + for (j = 0; j < rounds; j++) { + hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); + + /* add entries */ + for (i = 0; i < TEST_SIZE; i++) { + hashmap_entry_init(entries[i], hashes[i]); + hashmap_add(&map, entries[i]); + } + + hashmap_free(&map, 0); + } + } else { + /* test map lookups */ + hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); + + /* fill the map (sparsely if specified) */ + j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; + for (i = 0; i < j; i++) { + hashmap_entry_init(entries[i], hashes[i]); + hashmap_add(&map, entries[i]); + } + + for (j = 0; j < rounds; j++) { + for (i = 0; i < TEST_SIZE; i++) { + hashmap_get_from_hash(&map, hashes[i], + entries[i]->key); + } + } + + hashmap_free(&map, 0); + } +} + +#define DELIM " \t\r\n" + +/* + * Read stdin line by line and print result of commands to stdout: + * + * hash key -> strhash(key) memhash(key) strihash(key) memihash(key) + * put key value -> NULL / old value + * get key -> NULL / value + * remove key -> NULL / old value + * iterate -> key1 value1\nkey2 value2\n... + * size -> tablesize numentries + * + * perfhashmap method rounds -> test hashmap.[ch] performance + */ +int main(int argc, char *argv[]) +{ + char line[1024]; + struct hashmap map; + int icase; + + /* init hash map */ + icase = argc > 1 && !strcmp("ignorecase", argv[1]); + hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase + : test_entry_cmp), 0); + + /* process commands from stdin */ + while (fgets(line, sizeof(line), stdin)) { + char *cmd, *p1 = NULL, *p2 = NULL; + int l1 = 0, l2 = 0, hash = 0; + struct test_entry *entry; + + /* break line into command and up to two parameters */ + cmd = strtok(line, DELIM); + /* ignore empty lines */ + if (!cmd || *cmd == '#') + continue; + + p1 = strtok(NULL, DELIM); + if (p1) { + l1 = strlen(p1); + hash = icase ? strihash(p1) : strhash(p1); + p2 = strtok(NULL, DELIM); + if (p2) + l2 = strlen(p2); + } + + if (!strcmp("hash", cmd) && l1) { + + /* print results of different hash functions */ + printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1), + strihash(p1), memihash(p1, l1)); + + } else if (!strcmp("add", cmd) && l1 && l2) { + + /* create entry with key = p1, value = p2 */ + entry = alloc_test_entry(hash, p1, l1, p2, l2); + + /* add to hashmap */ + hashmap_add(&map, entry); + + } else if (!strcmp("put", cmd) && l1 && l2) { + + /* create entry with key = p1, value = p2 */ + entry = alloc_test_entry(hash, p1, l1, p2, l2); + + /* add / replace entry */ + entry = hashmap_put(&map, entry); + + /* print and free replaced entry, if any */ + puts(entry ? get_value(entry) : "NULL"); + free(entry); + + } else if (!strcmp("get", cmd) && l1) { + + /* lookup entry in hashmap */ + entry = hashmap_get_from_hash(&map, hash, p1); + + /* print result */ + if (!entry) + puts("NULL"); + while (entry) { + puts(get_value(entry)); + entry = hashmap_get_next(&map, entry); + } + + } else if (!strcmp("remove", cmd) && l1) { + + /* setup static key */ + struct hashmap_entry key; + hashmap_entry_init(&key, hash); + + /* remove entry from hashmap */ + entry = hashmap_remove(&map, &key, p1); + + /* print result and free entry*/ + puts(entry ? get_value(entry) : "NULL"); + free(entry); + + } else if (!strcmp("iterate", cmd)) { + + struct hashmap_iter iter; + hashmap_iter_init(&map, &iter); + while ((entry = hashmap_iter_next(&iter))) + printf("%s %s\n", entry->key, get_value(entry)); + + } else if (!strcmp("size", cmd)) { + + /* print table sizes */ + printf("%u %u\n", map.tablesize, map.size); + + } else if (!strcmp("intern", cmd) && l1) { + + /* test that strintern works */ + const char *i1 = strintern(p1); + const char *i2 = strintern(p1); + if (strcmp(i1, p1)) + printf("strintern(%s) returns %s\n", p1, i1); + else if (i1 == p1) + printf("strintern(%s) returns input pointer\n", p1); + else if (i1 != i2) + printf("strintern(%s) != strintern(%s)", i1, i2); + else + printf("%s\n", i1); + + } else if (!strcmp("perfhashmap", cmd) && l1 && l2) { + + perf_hashmap(atoi(p1), atoi(p2)); + + } else { + + printf("Unknown command %s\n", cmd); + + } + } + + hashmap_free(&map, 1); + return 0; +} diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c new file mode 100644 index 0000000000..05d4699c4a --- /dev/null +++ b/t/helper/test-index-version.c @@ -0,0 +1,14 @@ +#include "cache.h" + +int main(int argc, char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c new file mode 100644 index 0000000000..1e58f0476f --- /dev/null +++ b/t/helper/test-line-buffer.c @@ -0,0 +1,91 @@ +/* + * test-line-buffer.c: code to exercise the svn importer's input helper + */ + +#include "git-compat-util.h" +#include "strbuf.h" +#include "vcs-svn/line_buffer.h" + +static uint32_t strtouint32(const char *s) +{ + char *end; + uintmax_t n = strtoumax(s, &end, 10); + if (*s == '\0' || *end != '\0') + die("invalid count: %s", s); + return (uint32_t) n; +} + +static void handle_command(const char *command, const char *arg, struct line_buffer *buf) +{ + switch (*command) { + case 'b': + if (starts_with(command, "binary ")) { + struct strbuf sb = STRBUF_INIT; + strbuf_addch(&sb, '>'); + buffer_read_binary(buf, &sb, strtouint32(arg)); + fwrite(sb.buf, 1, sb.len, stdout); + strbuf_release(&sb); + return; + } + case 'c': + if (starts_with(command, "copy ")) { + buffer_copy_bytes(buf, strtouint32(arg)); + return; + } + case 's': + if (starts_with(command, "skip ")) { + buffer_skip_bytes(buf, strtouint32(arg)); + return; + } + default: + die("unrecognized command: %s", command); + } +} + +static void handle_line(const char *line, struct line_buffer *stdin_buf) +{ + const char *arg = strchr(line, ' '); + if (!arg) + die("no argument in line: %s", line); + handle_command(line, arg + 1, stdin_buf); +} + +int main(int argc, char *argv[]) +{ + struct line_buffer stdin_buf = LINE_BUFFER_INIT; + struct line_buffer file_buf = LINE_BUFFER_INIT; + struct line_buffer *input = &stdin_buf; + const char *filename; + char *s; + + if (argc == 1) + filename = NULL; + else if (argc == 2) + filename = argv[1]; + else + usage("test-line-buffer [file | &fd] < script"); + + if (buffer_init(&stdin_buf, NULL)) + die_errno("open error"); + if (filename) { + if (*filename == '&') { + if (buffer_fdinit(&file_buf, strtouint32(filename + 1))) + die_errno("error opening fd %s", filename + 1); + } else { + if (buffer_init(&file_buf, filename)) + die_errno("error opening %s", filename); + } + input = &file_buf; + } + + while ((s = buffer_read_line(&stdin_buf))) + handle_line(s, input); + + if (filename && buffer_deinit(&file_buf)) + die("error reading from %s", filename); + if (buffer_deinit(&stdin_buf)) + die("input error"); + if (ferror(stdout)) + die("output error"); + return 0; +} diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c new file mode 100644 index 0000000000..4dad7095f1 --- /dev/null +++ b/t/helper/test-match-trees.c @@ -0,0 +1,26 @@ +#include "cache.h" +#include "tree.h" + +int main(int ac, char **av) +{ + unsigned char hash1[20], hash2[20], shifted[20]; + struct tree *one, *two; + + setup_git_directory(); + + if (get_sha1(av[1], hash1)) + die("cannot parse %s as an object name", av[1]); + if (get_sha1(av[2], hash2)) + die("cannot parse %s as an object name", av[2]); + one = parse_tree_indirect(hash1); + if (!one) + die("not a tree-ish %s", av[1]); + two = parse_tree_indirect(hash2); + if (!two) + die("not a tree-ish %s", av[2]); + + shift_tree(one->object.oid.hash, two->object.oid.hash, shifted, -1); + printf("shifted: %s\n", sha1_to_hex(shifted)); + + exit(0); +} diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c new file mode 100644 index 0000000000..ea3b959e94 --- /dev/null +++ b/t/helper/test-mergesort.c @@ -0,0 +1,52 @@ +#include "cache.h" +#include "mergesort.h" + +struct line { + char *text; + struct line *next; +}; + +static void *get_next(const void *a) +{ + return ((const struct line *)a)->next; +} + +static void set_next(void *a, void *b) +{ + ((struct line *)a)->next = b; +} + +static int compare_strings(const void *a, const void *b) +{ + const struct line *x = a, *y = b; + return strcmp(x->text, y->text); +} + +int main(int argc, char **argv) +{ + struct line *line, *p = NULL, *lines = NULL; + struct strbuf sb = STRBUF_INIT; + + for (;;) { + if (strbuf_getwholeline(&sb, stdin, '\n')) + break; + line = xmalloc(sizeof(struct line)); + line->text = strbuf_detach(&sb, NULL); + if (p) { + line->next = p->next; + p->next = line; + } else { + line->next = NULL; + lines = line; + } + p = line; + } + + lines = llist_mergesort(lines, get_next, set_next, compare_strings); + + while (lines) { + printf("%s", lines->text); + lines = lines->next; + } + return 0; +} diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c new file mode 100644 index 0000000000..c8c54213a3 --- /dev/null +++ b/t/helper/test-mktemp.c @@ -0,0 +1,14 @@ +/* + * test-mktemp.c: code to exercise the creation of temporary files + */ +#include "git-compat-util.h" + +int main(int argc, char *argv[]) +{ + if (argc != 2) + usage("Expected 1 parameter defining the temporary file template"); + + xmkstemp(xstrdup(argv[1])); + + return 0; +} diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c new file mode 100644 index 0000000000..2c8c8f18ed --- /dev/null +++ b/t/helper/test-parse-options.c @@ -0,0 +1,104 @@ +#include "cache.h" +#include "parse-options.h" +#include "string-list.h" + +static int boolean = 0; +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 char *string = NULL; +static char *file = NULL; +static int ambiguous; +static struct string_list list; + +static int length_callback(const struct option *opt, const char *arg, int unset) +{ + printf("Callback: \"%s\", %d\n", + (arg ? arg : "not set"), unset); + if (unset) + return 1; /* do not support unset */ + + *(int *)opt->value = strlen(arg); + return 0; +} + +static int number_callback(const struct option *opt, const char *arg, int unset) +{ + *(int *)opt->value = strtol(arg, NULL, 10); + return 0; +} + +int main(int argc, char **argv) +{ + const char *prefix = "prefix/"; + const char *usage[] = { + "test-parse-options ", + NULL + }; + struct option options[] = { + OPT_BOOL(0, "yes", &boolean, "get a boolean"), + OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), + { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL, + "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), + OPT_BIT('4', "or4", &boolean, + "bitwise-or boolean with ...0100", 4), + OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), + OPT_GROUP(""), + OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), + OPT_DATE('t', NULL, ×tamp, "get timestamp of