diff options
240 files changed, 5075 insertions, 1944 deletions
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/.travis.yml b/.travis.yml index 78e433ba71..1fdcec8437 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,8 +22,11 @@ addons: env: global: - DEVELOPER=1 - - P4_VERSION="15.2" - - GIT_LFS_VERSION="1.1.0" + # The Linux build installs the defined dependency versions below. + # The OS X build installs the latest available versions. Keep that + # in mind when you encounter a broken OS X build! + - LINUX_P4_VERSION="16.1" + - LINUX_GIT_LFS_VERSION="1.2.0" - DEFAULT_TEST_TARGET=prove - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - GIT_TEST_OPTS="--verbose --tee" @@ -38,17 +41,17 @@ before_install: linux) mkdir --parents custom/p4 pushd custom/p4 - wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4d - wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4 + wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4d + wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4 chmod u+x p4d chmod u+x p4 export PATH="$(pwd):$PATH" popd mkdir --parents custom/git-lfs pushd custom/git-lfs - wget --quiet https://github.com/github/git-lfs/releases/download/v$GIT_LFS_VERSION/git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz - tar --extract --gunzip --file "git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz" - cp git-lfs-$GIT_LFS_VERSION/git-lfs . + wget --quiet https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz + tar --extract --gunzip --file "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" + cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs . export PATH="$(pwd):$PATH" popd ;; diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt new file mode 100644 index 0000000000..af184783bc --- /dev/null +++ b/Documentation/RelNotes/2.8.3.txt @@ -0,0 +1,70 @@ +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. + + * "git replace -e" did not honour "core.editor" configuration. + + * 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. + + * "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. + +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..8c3b52b352 --- /dev/null +++ b/Documentation/RelNotes/2.9.0.txt @@ -0,0 +1,354 @@ +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.<var>=<value> 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). + + * "git clone" learned "--shallow-submodules" option. + + * HTTP transport clients learned to throw extra HTTP headers at the + server, specified via http.extraHeader configuration variable. + + * Patch output from "git diff" and friends has been tweaked to be + more readable by using a blank line as a strong hint that the + contents before and after it belong to a logically separate unit. + + +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. + + * 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. + (merge 1fb3fb4 ld/p4-test-py3 later to maint). + + * As nobody maintains our in-tree git.spec.in and distros use their + own spec file, we stopped pretending that we support "make rpm". + + * Move from unsigned char[20] to struct object_id continues. + + +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[=<msg>] 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 <there>" 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 f924b52a 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). + + * "git rebase -m" could be asked to rebase an entire branch starting + from the root, but failed by assuming that there always is a parent + commit to the first commit on the branch. + (merge 79f4344 bw/rebase-merge-entire-branch later to maint). + + * Fix a broken "p4 lfs" test. + (merge 9e220fe ls/p4-lfs-test-fix-2.7.0 later to maint). + + * Other minor clean-ups and documentation updates + (merge 8b5a3e9 kn/for-each-tag-branch later to maint). + (merge 99dab16 sb/misc-cleanups later to maint). + (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). + (merge 376eb60 sb/config-exit-status-list later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 2cd6bdd7d2..ece0acdbab 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 @@ -1654,6 +1655,12 @@ http.emptyAuth:: a username in the URL, as libcurl normally requires a username for authentication. +http.extraHeader:: + Pass an additional HTTP header when communicating with a server. If + more than one such entry exists, all of them are added as extra + headers. To allow overriding the settings inherited from the system + config, an empty value will reset the extra headers to the empty list. + http.cookieFile:: File containing previously stored cookie lines which should be used in the Git http session, if they match the server. The file format @@ -1886,6 +1893,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 @@ -2147,8 +2162,11 @@ pack.packSizeLimit:: The maximum size of a pack. This setting only affects packing to a file when repacking, i.e. the git:// protocol is unaffected. It can be overridden by the `--max-pack-size` - option of linkgit:git-repack[1]. The minimum size allowed is - limited to 1 MiB. The default is unlimited. + option of linkgit:git-repack[1]. Reaching this limit results + in the creation of multiple packfiles; which in turn prevents + bitmaps from being created. + The minimum size allowed is limited to 1 MiB. + The default is unlimited. Common unit suffixes of 'k', 'm', or 'g' are supported. @@ -2548,8 +2566,9 @@ repack.writeBitmaps:: objects to disk (e.g., when `git repack -a` is run). This index can speed up the "counting objects" phase of subsequent packs created for clones and fetches, at the cost of some disk - space and extra time spent on the initial repack. Defaults to - false. + space and extra time spent on the initial repack. This has + no effect if multiple packfiles are created. + Defaults to false. rerere.autoUpdate:: When set to true, `git-rerere` updates the index with the @@ -2729,6 +2748,17 @@ submodule.<name>.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=<value>" 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<n>:: --unified=<n>:: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index b7c467a001..1b15cd7b16 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -14,8 +14,8 @@ SYNOPSIS [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] [--dissociate] [--separate-git-dir <git dir>] [--depth <depth>] [--[no-]single-branch] - [--recursive | --recurse-submodules] [--] <repository> - [<directory>] + [--recursive | --recurse-submodules] [--[no-]shallow-submodules] + [--jobs <n>] [--] <repository> [<directory>] DESCRIPTION ----------- @@ -191,7 +191,9 @@ objects from the source repository into a pack in the cloned repository. Create a 'shallow' clone with a history truncated to the specified number of commits. Implies `--single-branch` unless `--no-single-branch` is given to fetch the histories near the - tips of all branches. + tips of all branches. This implies `--shallow-submodules`. If + you want to have a shallow superproject clone, but full submodules, + also pass `--no-shallow-submodules`. --[no-]single-branch:: Clone only the history leading to the tip of a single branch, @@ -212,6 +214,9 @@ objects from the source repository into a pack in the cloned repository. repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) +--[no-]shallow-submodules:: + All submodules which are cloned will be shallow with a depth of 1. + --separate-git-dir=<git dir>:: Instead of placing the cloned repository where it is supposed to be, place the cloned repository at the specified directory, @@ -219,6 +224,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 <n>:: +--jobs <n>:: + The number of submodules fetched at the same time. + Defaults to the `submodule.fetchJobs` option. <repository>:: 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 [<object>]:: - 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 <strategy>] [-X <strategy-option>] [-S[<keyid>]] + [--[no-]allow-unrelated-histories] [--[no-]rerere-autoupdate] [-m <msg>] [<commit>...] 'git merge' <msg> HEAD <commit>... '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 <mail@address.com>" +------------- ++ +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-pack-objects.txt b/Documentation/git-pack-objects.txt index bbea5294ca..19cdcd0341 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -110,7 +110,8 @@ base-name:: --max-pack-size=<n>:: Maximum size of each output pack file. The size can be suffixed with "k", "m", or "g". The minimum size allowed is limited to 1 MiB. - If specified, multiple packfiles may be created. + If specified, multiple packfiles may be created, which also + prevents the creation of a bitmap index. The default is unlimited, unless the config variable `pack.packSizeLimit` is set. 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. <cmd> 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 <branch>, instead of diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index af230d0647..b9c02ce481 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -106,7 +106,8 @@ other objects in that pack they already have locally. --max-pack-size=<n>:: Maximum size of each output pack file. The size can be suffixed with "k", "m", or "g". The minimum size allowed is limited to 1 MiB. - If specified, multiple packfiles may be created. + If specified, multiple packfiles may be created, which also + prevents the creation of a bitmap index. The default is unlimited, unless the config variable `pack.packSizeLimit` is set. @@ -115,7 +116,8 @@ other objects in that pack they already have locally. Write a reachability bitmap index as part of the repack. This only makes sense when used with `-a` or `-A`, as the bitmaps must be able to refer to all reachable objects. This option - overrides the setting of `pack.writeBitmaps`. + overrides the setting of `repack.writeBitmaps`. This option + has no effect if multiple packfiles are created. --pack-kept-objects:: Include objects in `.keep` files when repacking. Note that we @@ -123,7 +125,7 @@ other objects in that pack they already have locally. This means that we may duplicate objects, but this makes the option safe to use when there are concurrent pushes or fetches. This option is generally only useful if you are writing bitmaps - with `-b` or `pack.writeBitmaps`, as it ensures that the + with `-b` or `repack.writeBitmaps`, as it ensures that the bitmapped packfile has the necessary objects. Configuration 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] [--] <path>... 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase|--merge] [--reference <repository>] - [--depth <depth>] [--recursive] [--] [<path>...] + [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...] 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...] 'git submodule' [--quiet] foreach [--recursive] <command> @@ -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 <n>:: +--jobs <n>:: + This option is only valid for the update command. + Clone new submodules in parallel with as many jobs. + Defaults to the `submodule.fetchJobs` option. <path>...:: 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 <new-branch>] <path> [<branch>] +'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree prune' [-n] [-v] [--expire <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 `<branch>`, 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/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=<n>:: +--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 '<n>') + 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[=<treeish>]:: 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/GIT-VERSION-GEN b/GIT-VERSION-GEN index 5f99f23cad..655b49011f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.8.2 +DEF_VER=v2.8.0.GIT LF=' ' @@ -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: # @@ -443,7 +440,6 @@ DIFF = diff TAR = tar FIND = find INSTALL = install -RPMBUILD = rpmbuild TCL_PATH = tclsh TCLTK_PATH = wish XGETTEXT = xgettext @@ -624,7 +620,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 +1134,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 +1897,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 +2204,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 +2224,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)) @@ -2396,31 +2389,25 @@ quick-install-html: ### Maintainer's dist rules -git.spec: git.spec.in GIT-VERSION-FILE - sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@+ - mv $@+ $@ - GIT_TARNAME = git-$(GIT_VERSION) -dist: git.spec git-archive$(X) configure +dist: git-archive$(X) configure ./git-archive --format=tar \ --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar @mkdir -p $(GIT_TARNAME) - @cp git.spec configure $(GIT_TARNAME) + @cp configure $(GIT_TARNAME) @echo $(GIT_VERSION) > $(GIT_TARNAME)/version @$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version $(TAR) rf $(GIT_TARNAME).tar \ - $(GIT_TARNAME)/git.spec \ $(GIT_TARNAME)/configure \ $(GIT_TARNAME)/version \ $(GIT_TARNAME)/git-gui/version @$(RM) -r $(GIT_TARNAME) gzip -f -9 $(GIT_TARNAME).tar -rpm: dist - $(RPMBUILD) \ - --define "_source_filedigest_algorithm md5" \ - --define "_binary_filedigest_algorithm md5" \ - -ta $(GIT_TARNAME).tar.gz +rpm:: + @echo >&2 "Use distro packaged sources to run rpmbuild" + @false +.PHONY: rpm htmldocs = git-htmldocs-$(GIT_VERSION) manpages = git-manpages-$(GIT_VERSION) @@ -2456,8 +2443,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) @@ -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 @@ -1 +1 @@ -Documentation/RelNotes/2.8.2.txt
\ No newline at end of file +Documentation/RelNotes/2.9.0.txt
\ No newline at end of file @@ -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; +} @@ -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/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 [<options>] [-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..5f867e67d8 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -40,6 +40,7 @@ static const char * const builtin_clone_usage[] = { static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; static int option_local = -1, option_no_hardlinks, option_shared, option_recursive; +static int option_shallow_submodules = -1; static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; @@ -51,6 +52,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 +75,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"), @@ -89,6 +93,8 @@ static struct option builtin_clone_options[] = { N_("create a shallow clone of that depth")), OPT_BOOL(0, "single-branch", &option_single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules, + N_("any cloned submodules will be shallow")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING_LIST('c', "config", &option_config, N_("key=value"), @@ -100,10 +106,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 +734,20 @@ 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 (option_shallow_submodules == 1 + || (option_shallow_submodules == -1 && option_depth)) + argv_array_push(&args, "--depth=1"); + + 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/grep.c b/builtin/grep.c index 111b6f6cf1..462e607901 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -438,7 +438,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len, + hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { @@ -447,10 +447,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_sha1_file(entry.sha1, &type, &size); + data = lock_and_read_sha1_file(entry.oid->hash, &type, &size); if (!data) die(_("unable to read tree (%s)"), - sha1_to_hex(entry.sha1)); + oid_to_hex(entry.oid)); strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); 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-tree.c b/builtin/merge-tree.c index ca570041df..5b7ab9b967 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -150,15 +150,15 @@ static void show_result(void) /* An empty entry never compares same, not even to another empty entry */ static int same_entry(struct name_entry *a, struct name_entry *b) { - return a->sha1 && - b->sha1 && - !hashcmp(a->sha1, b->sha1) && + return a->oid && + b->oid && + !oidcmp(a->oid, b->oid) && a->mode == b->mode; } static int both_empty(struct name_entry *a, struct name_entry *b) { - return !(a->sha1 || b->sha1); + return !(a->oid || b->oid); } static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) @@ -188,8 +188,8 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s return; path = traverse_path(info, result); - orig = create_entry(2, ours->mode, ours->sha1, path); - final = create_entry(0, result->mode, result->sha1, path); + orig = create_entry(2, ours->mode, ours->oid->hash, path); + final = create_entry(0, result->mode, result->oid->hash, path); final->link = orig; @@ -213,7 +213,7 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL) +#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid->hash : NULL) buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); @@ -239,7 +239,7 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info path = entry->path; else path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->sha1, path); + link = create_entry(stage, n->mode, n->oid->hash, path); link->link = entry; return link; } @@ -314,7 +314,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s } if (same_entry(entry+0, entry+1)) { - if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + if (entry[2].oid && !S_ISDIR(entry[2].mode)) { /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; 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 @@ copy_data: 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/pack-objects.c b/builtin/pack-objects.c index a27de5b323..14dccb5283 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -759,6 +759,10 @@ static off_t write_reused_pack(struct sha1file *f) return reuse_packfile_offset - sizeof(struct pack_header); } +static const char no_split_warning[] = N_( +"disabling bitmap writing, packs are split due to pack.packSizeLimit" +); + static void write_pack_file(void) { uint32_t i = 0, j; @@ -813,7 +817,10 @@ static void write_pack_file(void) fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written, sha1, offset); close(fd); - write_bitmap_index = 0; + if (write_bitmap_index) { + warning(_(no_split_warning)); + write_bitmap_index = 0; + } } if (!pack_to_stdout) { @@ -1186,7 +1193,7 @@ static void add_pbase_object(struct tree_desc *tree, if (cmp < 0) return; if (name[cmplen] != '/') { - add_object_entry(entry.sha1, + add_object_entry(entry.oid->hash, object_type(entry.mode), fullname, 1); return; @@ -1197,7 +1204,7 @@ static void add_pbase_object(struct tree_desc *tree, const char *down = name+cmplen+1; int downlen = name_cmp_len(down); - tree = pbase_tree_get(entry.sha1); + tree = pbase_tree_get(entry.oid->hash); if (!tree) return; init_tree_desc(&sub, tree->tree_data, tree->tree_size); 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")), @@ -306,6 +314,18 @@ static enum rebase_type config_get_rebase(void) } /** + * 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. */ static int has_unstaged_changes(const char *prefix) @@ -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 <git-dir>"; +static const char * const receive_pack_usage[] = { + N_("git receive-pack <git-dir>"), + 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/reflog.c b/builtin/reflog.c index 2d46b6482a..7a7136e53e 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -84,8 +84,8 @@ static int tree_is_complete(const unsigned char *sha1) init_tree_desc(&desc, tree->buffer, tree->size); complete = 1; while (tree_entry(&desc, &entry)) { - if (!has_sha1_file(entry.sha1) || - (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) { + if (!has_sha1_file(entry.oid->hash) || + (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid->hash))) { tree->object.flags |= INCOMPLETE; complete = 0; } 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/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=<path>] [--quiet] " - "[--reference <repository>] [--name <name>] [--url <url>]" - "[--depth <depth>] [--] [<path>...]"), + "[--reference <repository>] [--name <name>] [--depth <depth>] " + "--url <url> --path <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, "<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=<path>] [<path>...]"), + 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")); @@ -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-tree.c b/cache-tree.c index 3ebf9c3aa4..ddf0cc9f9a 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -663,7 +663,7 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) cnt++; else { struct cache_tree_sub *sub; - struct tree *subtree = lookup_tree(entry.sha1); + struct tree *subtree = lookup_tree(entry.oid->hash); if (!subtree->object.parsed) parse_tree(subtree); sub = cache_tree_sub(it, entry.path); @@ -710,7 +710,7 @@ int cache_tree_matches_traversal(struct cache_tree *root, it = find_cache_tree_from_traversal(root, info); it = cache_tree_find(it, ent->path); - if (it && it->entry_count > 0 && !hashcmp(ent->sha1, it->sha1)) + if (it && it->entry_count > 0 && !hashcmp(ent->oid->hash, it->sha1)) return it->entry_count; return 0; } @@ -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); @@ -1124,6 +1154,8 @@ extern int get_sha1_blob(const char *str, unsigned char *sha1); extern void maybe_die_on_misspelt_object_name(const char *name, const char *prefix); extern int get_sha1_with_context(const char *str, unsigned flags, unsigned char *sha1, struct object_context *orc); +extern int get_oid(const char *str, struct object_id *oid); + typedef int each_abbrev_fn(const unsigned char *sha1, void *); extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *); @@ -1526,7 +1558,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 +1581,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); @@ -1736,8 +1766,8 @@ int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int extern int diff_auto_refresh_index; /* match-trees.c */ -void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); -void shift_tree_by(const unsigned char *, const unsigned char *, unsigned char *, const char *); +void shift_tree(const struct object_id *, const struct object_id *, struct object_id *, int); +void shift_tree_by(const struct object_id *, const struct object_id *, struct object_id *, const char *); /* * whitespace rules. @@ -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 <CommonCrypto/CommonHMAC.h> -#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 <Security/Security.h> 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) @@ -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) @@ -1313,14 +1309,11 @@ static struct config_set_element *configset_find_element(struct config_set *cs, struct config_set_element k; struct config_set_element *found_entry; char *normalized_key; - int ret; /* * `key` may come from the user, so normalize it before using it * for querying entries from the hashmap. */ - ret = git_config_parse_key(key, &normalized_key, NULL); - - if (ret) + if (git_config_parse_key(key, &normalized_key, NULL)) return NULL; hashmap_entry_init(&k, strhash(normalized_key)); @@ -2221,9 +2214,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 +2401,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 <matthieu.moy@grenoble-inp.fr>. 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 <https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__. -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 `<doc/troubleshooting.rst>`__ 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 `<doc/gitolite.rst>`__ - * 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 + ``%(<variable>)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 ``<a href="foo">link</a>``, 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 `<doc/customizing-emails.rst>`__ 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 2>/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 = """\ + <span style="color:#808080">This is an automated email from the git hooks/post-receive script.</span><br /><br /> + + <strong>%(pusher)s</strong> pushed a commit to %(refname_type)s %(short_refname)s + in repository %(repo_shortname)s.<br /> + + <a href="https://github.com/git-multimail/git-multimail/commit/%(newrev)s">View on GitHub</a>. + """ + +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 <Foo.Bar@example.com> + remote: Reply-To: Auth Or <Foo.Bar@example.com> + 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: <sender-field> on behalf of +<from-field>``). 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 subprocess 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 @@ if PYTHON3: 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 @@ in repository %(repo_shortname)s. """ +LINK_TEXT_TEMPLATE = """\ +View the commit online: +%(browse_url)s + +""" + +LINK_HTML_TEMPLATE = """\ +<p><a href="%(browse_url)s">View the commit online</a>.</p> +""" + REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE @@ -532,6 +556,28 @@ class Config(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): 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 @@ class Change(object): yield line if self._contains_html_diff: yield '</pre>' - - 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 @@ class Revision(Change): 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 @@ class Revision(Change): ): 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 @@ class Revision(Change): 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 @@ class ReferenceChange(Change): ): 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 @@ class ReferenceChange(Change): 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 @@ class SMTPMailer(Mailer): smtpservertimeout=10.0, smtpserverdebuglevel=0, smtpencryption='none', smtpuser='', smtppass='', + smtpcacerts='' ): if not envelopesender: sys.stderr.write( @@ -1915,6 +2015,7 @@ class SMTPMailer(Mailer): self.security = smtpencryption self.username = smtpuser self.password = smtppass + self.smtpcacerts = smtpcacerts try: def call(klass, server, timeout): try: @@ -1925,13 +2026,56 @@ class SMTPMailer(Mailer): 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 @@ class SMTPMailer(Mailer): def __del__(self): if hasattr(self, 'smtp'): self.smtp.quit() + del self.smtp def send(self, lines, to_addrs): try: @@ -1958,13 +2103,24 @@ class SMTPMailer(Mailer): 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 @@ class Environment(object): 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 @@ class Environment(object): 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 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): 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 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin): ['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); } @@ -26,6 +26,7 @@ #endif static int diff_detect_rename_default; +static int diff_compaction_heuristic = 1; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; @@ -168,6 +169,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")) { @@ -184,6 +190,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_detect_rename_default = git_config_rename(var, value); return 0; } + if (!strcmp(var, "diff.compactionheuristic")) { + diff_compaction_heuristic = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "diff.autorefreshindex")) { diff_auto_refresh_index = git_config_bool(var, value); return 0; @@ -3273,6 +3283,8 @@ void diff_setup(struct diff_options *options) options->use_color = diff_use_color_default; options->detect_rename = diff_detect_rename_default; options->xdl_opts |= diff_algorithm; + if (diff_compaction_heuristic) + DIFF_XDL_SET(options, COMPACTION_HEURISTIC); options->orderfile = diff_order_file_cfg; @@ -3793,6 +3805,10 @@ int diff_opt_parse(struct diff_options *options, DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--ignore-blank-lines")) DIFF_XDL_SET(options, IGNORE_BLANK_LINES); + else if (!strcmp(arg, "--compaction-heuristic")) + DIFF_XDL_SET(options, COMPACTION_HEURISTIC); + else if (!strcmp(arg, "--no-compaction-heuristic")) + DIFF_XDL_CLR(options, COMPACTION_HEURISTIC); else if (!strcmp(arg, "--patience")) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) @@ -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 *); @@ -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) @@ -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"); @@ -312,9 +312,9 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op if (S_ISGITLINK(entry.mode)) continue; if (S_ISDIR(entry.mode)) - result = options->walk(&lookup_tree(entry.sha1)->object, OBJ_TREE, data, options); + result = options->walk(&lookup_tree(entry.oid->hash)->object, OBJ_TREE, data, options); else if (S_ISREG(entry.mode) || S_ISLNK(entry.mode)) - result = options->walk(&lookup_blob(entry.sha1)->object, OBJ_BLOB, data, options); + result = options->walk(&lookup_blob(entry.oid->hash)->object, OBJ_BLOB, data, options); else { result = error("in tree %s: entry %s has bad mode %.6o", oid_to_hex(&tree->object.oid), entry.path, entry.mode); @@ -450,11 +450,11 @@ static int fsck_tree(struct tree *item, struct fsck_options *options) while (desc.size) { unsigned mode; const char *name; - const unsigned char *sha1; + const struct object_id *oid; - sha1 = tree_entry_extract(&desc, &name, &mode); + oid = tree_entry_extract(&desc, &name, &mode); - has_null_sha1 |= is_null_sha1(sha1); + has_null_sha1 |= is_null_oid(oid); has_full_path |= !!strchr(name, '/'); has_empty_name |= !*name; has_dot |= !strcmp(name, "."); 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 ($diff_new_color) = 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 <openssl/ssl.h> #include <openssl/err.h> -#ifdef NO_HMAC_CTX_CLEANUP -#define HMAC_CTX_cleanup HMAC_cleanup -#endif #endif /* On most systems <netdb.h> 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]*) @@ -1064,8 +1064,15 @@ class GitLFS(LargeFileSystem): if pointerProcess.wait(): os.remove(contentFile) die('git-lfs pointer command failed. Did you install the extension?') - pointerContents = [i+'\n' for i in pointerFile.split('\n')[2:][:-1]] - oid = pointerContents[1].split(' ')[1].split(':')[1][:-1] + + # Git LFS removed the preamble in the output of the 'pointer' command + # starting from version 1.2.0. Check for the preamble here to support + # earlier versions. + # c.f. https://github.com/github/git-lfs/commit/da2935d9a739592bc775c98d8ef4df9c72ea3b43 + if pointerFile.startswith('Git LFS pointer for'): + pointerFile = re.sub(r'Git LFS pointer for.*\n\n', '', pointerFile) + + oid = re.search(r'^oid \w+:(\w+)', pointerFile, re.MULTILINE).group(1) localLargeFile = os.path.join( os.getcwd(), '.git', 'lfs', 'objects', oid[:2], oid[2:4], @@ -1073,7 +1080,7 @@ class GitLFS(LargeFileSystem): ) # LFS Spec states that pointer files should not have the executable bit set. gitMode = '100644' - return (gitMode, pointerContents, localLargeFile) + return (gitMode, pointerFile, localLargeFile) def pushFile(self, localLargeFile): uploadProcess = subprocess.Popen( @@ -1160,6 +1167,15 @@ class P4UserMap: 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 +2327,15 @@ class P4Sync(Command, P4UserMap): 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 +2681,7 @@ class P4Sync(Command, P4UserMap): 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 +2709,8 @@ class P4Sync(Command, P4UserMap): self.gitStream.write("data <<EOT\n") self.gitStream.write(details["desc"]) + if len(jobs) > 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--merge.sh b/git-rebase--merge.sh index 2cc2a6d273..8d43db9069 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -67,7 +67,9 @@ call_merge () { GIT_MERGE_VERBOSITY=1 && export GIT_MERGE_VERBOSITY fi test -z "$strategy" && strategy=recursive - eval 'git-merge-$strategy' $strategy_opts '"$cmt^" -- "$hd" "$cmt"' + # If cmt doesn't have a parent, don't include it as a base + base=$(git rev-parse --verify --quiet $cmt^) + eval 'git-merge-$strategy' $strategy_opts $base ' -- "$hd" "$cmt"' rv=$? case "$rv" in 0) 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 @@ if (defined $sender) { # 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 @@ my ($message_id_stamp, $message_id_serial); 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/git.spec.in b/git.spec.in deleted file mode 100644 index bfd1cfb63f..0000000000 --- a/git.spec.in +++ /dev/null @@ -1,330 +0,0 @@ -# Pass --without docs to rpmbuild if you don't want the documentation - -Name: git -Version: @@VERSION@@ -Release: 1%{?dist} -Summary: Core git tools -License: GPL -Group: Development/Tools -URL: http://kernel.org/pub/software/scm/git/ -Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz -BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettext %{!?_without_docs:, xmlto, asciidoc > 6.0.3} -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -Requires: perl-Git = %{version}-%{release} -Requires: zlib >= 1.2, rsync, less, openssh-clients, expat -Provides: git-core = %{version}-%{release} -Obsoletes: git-core <= 1.5.4.2 -Obsoletes: git-p4 - -%description -Git is a fast, scalable, distributed revision control system with an -unusually rich command set that provides both high-level operations -and full access to internals. - -The git rpm installs the core tools with minimal dependencies. To -install all git packages, including tools for integrating with other -SCMs, install the git-all meta-package. - -%package all -Summary: Meta-package to pull in all git tools -Group: Development/Tools -Requires: git = %{version}-%{release} -Requires: git-svn = %{version}-%{release} -Requires: git-cvs = %{version}-%{release} -Requires: git-arch = %{version}-%{release} -Requires: git-email = %{version}-%{release} -Requires: gitk = %{version}-%{release} -Requires: gitweb = %{version}-%{release} -Requires: git-gui = %{version}-%{release} -Obsoletes: git <= 1.5.4.2 - -%description all -Git is a fast, scalable, distributed revision control system with an -unusually rich command set that provides both high-level operations -and full access to internals. - -This is a dummy package which brings in all subpackages. - -%package svn -Summary: Git tools for importing Subversion repositories -Group: Development/Tools -Requires: git = %{version}-%{release}, subversion -%description svn -Git tools for importing Subversion repositories. - -%package cvs -Summary: Git tools for importing CVS repositories -Group: Development/Tools -Requires: git = %{version}-%{release}, cvs, cvsps -%description cvs -Git tools for importing CVS repositories. - -%package arch -Summary: Git tools for importing Arch repositories -Group: Development/Tools -Requires: git = %{version}-%{release}, tla -%description arch -Git tools for importing Arch repositories. - -%package email -Summary: Git tools for sending email -Group: Development/Tools -Requires: git = %{version}-%{release} -%description email -Git tools for sending email. - -%package gui -Summary: Git GUI tool -Group: Development/Tools -Requires: git = %{version}-%{release}, tk >= 8.4 -%description gui -Git GUI tool - -%package -n gitk -Summary: Git revision tree visualiser ('gitk') -Group: Development/Tools -Requires: git = %{version}-%{release}, tk >= 8.4 -%description -n gitk -Git revision tree visualiser ('gitk') - -%package -n gitweb -Summary: Git web interface -Group: Development/Tools -Requires: git = %{version}-%{release} -%description -n gitweb -Browsing git repository on the web - -%package -n perl-Git -Summary: Perl interface to Git -Group: Development/Libraries -Requires: git = %{version}-%{release} -Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) -BuildRequires: perl(Error) -BuildRequires: perl(ExtUtils::MakeMaker) - -%description -n perl-Git -Perl interface to Git - -%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version} -%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} - -%prep -%setup -q - -%build -make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" \ - %{path_settings} \ - all %{!?_without_docs: doc} - -%install -rm -rf $RPM_BUILD_ROOT -make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \ - %{path_settings} \ - INSTALLDIRS=vendor install %{!?_without_docs: install-doc} -test ! -d $RPM_BUILD_ROOT%{python_sitelib} || rm -fr $RPM_BUILD_ROOT%{python_sitelib} -find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' -find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';' -find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';' - -(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files -(find $RPM_BUILD_ROOT%{_libexecdir}/git-core -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@) >> bin-man-doc-files -(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files -%if %{!?_without_docs:1}0 -(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files -%else -rm -rf $RPM_BUILD_ROOT%{_mandir} -%endif -rm -rf $RPM_BUILD_ROOT%{_datadir}/locale - -mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d -install -m 644 -T contrib/completion/git-completion.bash $RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d/git - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -f bin-man-doc-files -%defattr(-,root,root) -%{_datadir}/git-core/ -%doc README.md COPYING Documentation/*.txt -%{!?_without_docs: %doc Documentation/*.html Documentation/howto} -%{!?_without_docs: %doc Documentation/technical} -%{_sysconfdir}/bash_completion.d - -%files svn -%defattr(-,root,root) -%{_libexecdir}/git-core/*svn* -%doc Documentation/*svn*.txt -%{!?_without_docs: %{_mandir}/man1/*svn*.1*} -%{!?_without_docs: %doc Documentation/*svn*.html } - -%files cvs -%defattr(-,root,root) -%doc Documentation/*git-cvs*.txt -%{_bindir}/git-cvsserver -%{_libexecdir}/git-core/*cvs* -%{!?_without_docs: %{_mandir}/man1/*cvs*.1*} -%{!?_without_docs: %doc Documentation/*git-cvs*.html } - -%files arch -%defattr(-,root,root) -%doc Documentation/git-archimport.txt -%{_libexecdir}/git-core/git-archimport -%{!?_without_docs: %{_mandir}/man1/git-archimport.1*} -%{!?_without_docs: %doc Documentation/git-archimport.html } - -%files email -%defattr(-,root,root) -%doc Documentation/*email*.txt -%{_libexecdir}/git-core/*email* -%{!?_without_docs: %{_mandir}/man1/*email*.1*} -%{!?_without_docs: %doc Documentation/*email*.html } - -%files gui -%defattr(-,root,root) -%{_libexecdir}/git-core/git-gui -%{_libexecdir}/git-core/git-citool -%{_libexecdir}/git-core/git-gui--askpass -%{_datadir}/git-gui/ -%{!?_without_docs: %{_mandir}/man1/git-gui.1*} -%{!?_without_docs: %doc Documentation/git-gui.html} -%{!?_without_docs: %{_mandir}/man1/git-citool.1*} -%{!?_without_docs: %doc Documentation/git-citool.html} - -%files -n gitk -%defattr(-,root,root) -%doc Documentation/*gitk*.txt -%{_bindir}/*gitk* -%{_datadir}/gitk/ -%{!?_without_docs: %{_mandir}/man1/*gitk*.1*} -%{!?_without_docs: %doc Documentation/*gitk*.html } - -%files -n gitweb -%defattr(-,root,root) -%doc gitweb/README gitweb/INSTALL Documentation/*gitweb*.txt -%{_datadir}/gitweb -%{!?_without_docs: %{_mandir}/man1/*gitweb*.1*} -%{!?_without_docs: %{_mandir}/man5/*gitweb*.5*} -%{!?_without_docs: %doc Documentation/*gitweb*.html } - -%files -n perl-Git -f perl-files -%defattr(-,root,root) - -%files all -# No files for you! - -%changelog -* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com> -- Add gitweb manpages to 'gitweb' subpackage - -* Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com> -- Add 'gitweb' subpackage. - -* Fri Mar 26 2010 Ian Ward Comfort <icomfort@stanford.edu> -- Ship bash completion support from contrib/ in the core package. - -* Sun Jan 31 2010 Junio C Hamano <gitster@pobox.com> -- Do not use %define inside %{!?...} construct. - -* Sat Jan 30 2010 Junio C Hamano <gitster@pobox.com> -- We don't ship Python bits until a real foreign scm interface comes. - -* Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com> -- fixed broken git help -w after renaming the git-core package to git. - -* Fri Sep 12 2008 Quy Tonthat <qtonthat@gmail.com> -- move git-cvsserver to bindir. - -* Sun Jun 15 2008 Junio C Hamano <gitster@pobox.com> -- Remove curl from Requires list. - -* Fri Feb 15 2008 Kristian HΓΈgsberg <krh@redhat.com> -- Rename git-core to just git and rename meta package from git to git-all. - -* Sun Feb 03 2008 James Bowes <jbowes@dangerouslyinc.com> -- Add a BuildRequires for gettext - -* Fri Jan 11 2008 Junio C Hamano <gitster@pobox.com> -- Include gitk message files - -* Sun Jan 06 2008 James Bowes <jbowes@dangerouslyinc.com> -- Make the metapackage require the same version of the subpackages. - -* Wed Dec 12 2007 Junio C Hamano <gitster@pobox.com> -- Adjust htmldir to point at /usr/share/doc/git-core-$version/ - -* Sun Jul 15 2007 Sean Estabrooks <seanlkml@sympatico.ca> -- Removed p4import. - -* Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com> -- Fixed problems looking for wrong manpages. - -* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org> -- Added documentation files for git-gui - -* Tue May 13 2007 Quy Tonthat <qtonthat@gmail.com> -- Added lib files for git-gui -- Added Documentation/technical (As needed by Git Users Manual) - -* Tue May 8 2007 Quy Tonthat <qtonthat@gmail.com> -- Added howto files - -* Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru> -- Added the git-p4 package: Perforce import stuff. - -* Mon Feb 13 2007 Nicolas Pitre <nico@fluxnic.net> -- Update core package description (Git isn't as stupid as it used to be) - -* Mon Feb 12 2007 Junio C Hamano <junkio@cox.net> -- Add git-gui and git-citool. - -* Mon Nov 14 2005 H. Peter Anvin <hpa@zytor.com> 0.99.9j-1 -- Change subpackage names to git-<name> instead of git-core-<name> -- Create empty root package which brings in all subpackages -- Rename git-tk -> gitk - -* Thu Nov 10 2005 Chris Wright <chrisw@osdl.org> 0.99.9g-1 -- zlib dependency fix -- Minor cleanups from split -- Move arch import to separate package as well - -* Tue Sep 27 2005 Jim Radford <radford@blackbean.org> -- Move programs with non-standard dependencies (svn, cvs, email) - into separate packages - -* Tue Sep 27 2005 H. Peter Anvin <hpa@zytor.com> -- parallelize build -- COPTS -> CFLAGS - -* Fri Sep 16 2005 Chris Wright <chrisw@osdl.org> 0.99.6-1 -- update to 0.99.6 - -* Fri Sep 16 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl> -- Linus noticed that less is required, added to the dependencies - -* Sun Sep 11 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl> -- Updated dependencies -- Don't assume manpages are gzipped - -* Thu Aug 18 2005 Chris Wright <chrisw@osdl.org> 0.99.4-4 -- drop sh_utils, sh-utils, diffutils, mktemp, and openssl Requires -- use RPM_OPT_FLAGS in spec file, drop patch0 - -* Wed Aug 17 2005 Tom "spot" Callaway <tcallawa@redhat.com> 0.99.4-3 -- use dist tag to differentiate between branches -- use rpm optflags by default (patch0) -- own %{_datadir}/git-core/ - -* Mon Aug 15 2005 Chris Wright <chrisw@osdl.org> -- update spec file to fix Buildroot, Requires, and drop Vendor - -* Sun Aug 07 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl> -- Redid the description -- Cut overlong make line, loosened changelog a bit -- I think Junio (or perhaps OSDL?) should be vendor... - -* Thu Jul 14 2005 Eric Biederman <ebiederm@xmission.com> -- Add the man pages, and the --without docs build option - -* Wed Jul 7 2005 Chris Wright <chris@osdl.org> -- initial git spec file 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-push.c b/http-push.c index bd60668707..a092f0288b 100644 --- a/http-push.c +++ b/http-push.c @@ -211,7 +211,7 @@ static void curl_setup_http(CURL *curl, const char *url, static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) { struct strbuf buf = STRBUF_INIT; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers = http_copy_default_headers(); if (options & DAV_HEADER_IF) { strbuf_addf(&buf, "If: (<%s>)", lock->token); @@ -417,7 +417,7 @@ static void start_put(struct transfer_request *request) static void start_move(struct transfer_request *request) { struct active_request_slot *slot; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers = http_copy_default_headers(); slot = get_active_slot(); slot->callback_func = process_response; @@ -845,7 +845,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) char *ep; char timeout_header[25]; struct remote_lock *lock = NULL; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers = http_copy_default_headers(); struct xml_ctx ctx; char *escaped; @@ -1126,7 +1126,7 @@ static void remote_ls(const char *path, int flags, struct slot_results results; struct strbuf in_buffer = STRBUF_INIT; struct buffer out_buffer = { STRBUF_INIT, 0 }; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers = http_copy_default_headers(); struct xml_ctx ctx; struct remote_ls_ctx ls; @@ -1204,7 +1204,7 @@ static int locking_available(void) struct slot_results results; struct strbuf in_buffer = STRBUF_INIT; struct buffer out_buffer = { STRBUF_INIT, 0 }; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers = http_copy_default_headers(); struct xml_ctx ctx; int lock_flags = 0; char *escaped; @@ -1312,10 +1312,10 @@ static struct object_list **process_tree(struct tree *tree, while (tree_entry(&desc, &entry)) switch (object_type(entry.mode)) { case OBJ_TREE: - p = process_tree(lookup_tree(entry.sha1), p); + p = process_tree(lookup_tree(entry.oid->hash), p); break; case OBJ_BLOB: - p = process_blob(lookup_blob(entry.sha1), p); + p = process_blob(lookup_blob(entry.oid->hash), p); break; default: /* Subproject commit - not in this repository */ @@ -114,6 +114,7 @@ static unsigned long http_auth_methods = CURLAUTH_ANY; static struct curl_slist *pragma_header; static struct curl_slist *no_pragma_header; +static struct curl_slist *extra_http_headers; static struct active_request_slot *active_queue_head; @@ -323,6 +324,19 @@ static int http_options(const char *var, const char *value, void *cb) #endif } + if (!strcmp("http.extraheader", var)) { + if (!value) { + return config_error_nonbool(var); + } else if (!*value) { + curl_slist_free_all(extra_http_headers); + extra_http_headers = NULL; + } else { + extra_http_headers = + curl_slist_append(extra_http_headers, value); + } + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, cb); } @@ -605,7 +619,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")) @@ -675,8 +692,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) if (remote) var_override(&http_proxy_authmethod, remote->http_proxy_authmethod); - pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); - no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + pragma_header = curl_slist_append(http_copy_default_headers(), + "Pragma: no-cache"); + no_pragma_header = curl_slist_append(http_copy_default_headers(), + "Pragma:"); #ifdef USE_CURL_MULTI { @@ -762,6 +781,9 @@ void http_cleanup(void) #endif curl_global_cleanup(); + curl_slist_free_all(extra_http_headers); + extra_http_headers = NULL; + curl_slist_free_all(pragma_header); pragma_header = NULL; @@ -1160,6 +1182,16 @@ int run_one_slot(struct active_request_slot *slot, return handle_curl_result(results); } +struct curl_slist *http_copy_default_headers(void) +{ + struct curl_slist *headers = NULL, *h; + + for (h = extra_http_headers; h; h = h->next) + headers = curl_slist_append(headers, h->data); + + return headers; +} + static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf) { char *ptr; @@ -1377,7 +1409,7 @@ static int http_request(const char *url, { struct active_request_slot *slot; struct slot_results results; - struct curl_slist *headers = NULL; + struct curl_slist *headers = http_copy_default_headers(); struct strbuf buf = STRBUF_INIT; const char *accept_language; int ret; @@ -106,6 +106,7 @@ extern void step_active_slots(void); extern void http_init(struct remote *remote, const char *url, int proactive_auth); extern void http_cleanup(void); +extern struct curl_slist *http_copy_default_headers(void); extern long int git_curl_ipresolve; extern int active_requests; @@ -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: "<user> <digest in hex>" */ 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/list-objects.c b/list-objects.c index 917cc5d7c9..f3ca6aafb7 100644 --- a/list-objects.c +++ b/list-objects.c @@ -110,16 +110,16 @@ static void process_tree(struct rev_info *revs, if (S_ISDIR(entry.mode)) process_tree(revs, - lookup_tree(entry.sha1), + lookup_tree(entry.oid->hash), show, base, entry.path, cb_data); else if (S_ISGITLINK(entry.mode)) - process_gitlink(revs, entry.sha1, + process_gitlink(revs, entry.oid->hash, show, base, entry.path, cb_data); else process_blob(revs, - lookup_blob(entry.sha1), + lookup_blob(entry.oid->hash), show, base, entry.path, cb_data); } 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/match-trees.c b/match-trees.c index 1ce0954a3e..396b7338df 100644 --- a/match-trees.c +++ b/match-trees.c @@ -48,17 +48,17 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path) } static void *fill_tree_desc_strict(struct tree_desc *desc, - const unsigned char *hash) + const struct object_id *hash) { void *buffer; enum object_type type; unsigned long size; - buffer = read_sha1_file(hash, &type, &size); + buffer = read_sha1_file(hash->hash, &type, &size); if (!buffer) - die("unable to read tree (%s)", sha1_to_hex(hash)); + die("unable to read tree (%s)", oid_to_hex(hash)); if (type != OBJ_TREE) - die("%s is not a tree", sha1_to_hex(hash)); + die("%s is not a tree", oid_to_hex(hash)); init_tree_desc(desc, buffer, size); return buffer; } @@ -73,7 +73,7 @@ static int base_name_entries_compare(const struct name_entry *a, /* * Inspect two trees, and give a score that tells how similar they are. */ -static int score_trees(const unsigned char *hash1, const unsigned char *hash2) +static int score_trees(const struct object_id *hash1, const struct object_id *hash2) { struct tree_desc one; struct tree_desc two; @@ -104,7 +104,7 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2) else if (cmp > 0) /* path2 does not appear in one */ score += score_missing(e2.mode, e2.path); - else if (hashcmp(e1.sha1, e2.sha1)) + else if (oidcmp(e1.oid, e2.oid)) /* they are different */ score += score_differs(e1.mode, e2.mode, e1.path); else @@ -119,8 +119,8 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2) /* * Match one itself and its subtrees with two and pick the best match. */ -static void match_trees(const unsigned char *hash1, - const unsigned char *hash2, +static void match_trees(const struct object_id *hash1, + const struct object_id *hash2, int *best_score, char **best_match, const char *base, @@ -131,7 +131,7 @@ static void match_trees(const unsigned char *hash1, while (one.size) { const char *path; - const unsigned char *elem; + const struct object_id *elem; unsigned mode; int score; @@ -191,15 +191,15 @@ static int splice_tree(const unsigned char *hash1, while (desc.size) { const char *name; unsigned mode; - const unsigned char *sha1; + const struct object_id *oid; - sha1 = tree_entry_extract(&desc, &name, &mode); + oid = tree_entry_extract(&desc, &name, &mode); if (strlen(name) == toplen && !memcmp(name, prefix, toplen)) { if (!S_ISDIR(mode)) die("entry %s in tree %s is not a tree", name, sha1_to_hex(hash1)); - rewrite_here = (unsigned char *) sha1; + rewrite_here = (unsigned char *) oid->hash; break; } update_tree_entry(&desc); @@ -229,9 +229,9 @@ static int splice_tree(const unsigned char *hash1, * other hand, it could cover tree one and we might need to pick a * subtree of it. */ -void shift_tree(const unsigned char *hash1, - const unsigned char *hash2, - unsigned char *shifted, +void shift_tree(const struct object_id *hash1, + const struct object_id *hash2, + struct object_id *shifted, int depth_limit) { char *add_prefix; @@ -262,7 +262,7 @@ void shift_tree(const unsigned char *hash1, match_trees(hash2, hash1, &del_score, &del_prefix, "", depth_limit); /* Assume we do not have to do any shifting */ - hashcpy(shifted, hash2); + oidcpy(shifted, hash2); if (add_score < del_score) { /* We need to pick a subtree of two */ @@ -271,16 +271,16 @@ void shift_tree(const unsigned char *hash1, if (!*del_prefix) return; - if (get_tree_entry(hash2, del_prefix, shifted, &mode)) + if (get_tree_entry(hash2->hash, del_prefix, shifted->hash, &mode)) die("cannot find path %s in tree %s", - del_prefix, sha1_to_hex(hash2)); + del_prefix, oid_to_hex(hash2)); return; } if (!*add_prefix) return; - splice_tree(hash1, add_prefix, hash2, shifted); + splice_tree(hash1->hash, add_prefix, hash2->hash, shifted->hash); } /* @@ -288,22 +288,22 @@ void shift_tree(const unsigned char *hash1, * Unfortunately we cannot fundamentally tell which one to * be prefixed, as recursive merge can work in either direction. */ -void shift_tree_by(const unsigned char *hash1, - const unsigned char *hash2, - unsigned char *shifted, +void shift_tree_by(const struct object_id *hash1, + const struct object_id *hash2, + struct object_id *shifted, const char *shift_prefix) { - unsigned char sub1[20], sub2[20]; + struct object_id sub1, sub2; unsigned mode1, mode2; unsigned candidate = 0; /* Can hash2 be a tree at shift_prefix in tree hash1? */ - if (!get_tree_entry(hash1, shift_prefix, sub1, &mode1) && + if (!get_tree_entry(hash1->hash, shift_prefix, sub1.hash, &mode1) && S_ISDIR(mode1)) candidate |= 1; /* Can hash1 be a tree at shift_prefix in tree hash2? */ - if (!get_tree_entry(hash2, shift_prefix, sub2, &mode2) && + if (!get_tree_entry(hash2->hash, shift_prefix, sub2.hash, &mode2) && S_ISDIR(mode2)) candidate |= 2; @@ -313,19 +313,19 @@ void shift_tree_by(const unsigned char *hash1, int score; candidate = 0; - score = score_trees(sub1, hash2); + score = score_trees(&sub1, hash2); if (score > best_score) { candidate = 1; best_score = score; } - score = score_trees(sub2, hash1); + score = score_trees(&sub2, hash1); if (score > best_score) candidate = 2; } if (!candidate) { /* Neither is plausible -- do not shift */ - hashcpy(shifted, hash2); + oidcpy(shifted, hash2); return; } @@ -334,11 +334,11 @@ void shift_tree_by(const unsigned char *hash1, * shift tree2 down by adding shift_prefix above it * to match tree1. */ - splice_tree(hash1, shift_prefix, hash2, shifted); + splice_tree(hash1->hash, shift_prefix, hash2->hash, shifted->hash); else /* * shift tree2 up by removing shift_prefix from it * to match tree1. */ - hashcpy(shifted, sub2); + oidcpy(shifted, &sub2); } diff --git a/merge-recursive.c b/merge-recursive.c index b880ae50e7..65cb5d6c1f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -29,9 +29,9 @@ static struct tree *shift_tree_object(struct tree *one, struct tree *two, struct object_id shifted; if (!*subtree_shift) { - shift_tree(one->object.oid.hash, two->object.oid.hash, shifted.hash, 0); + shift_tree(&one->object.oid, &two->object.oid, &shifted, 0); } else { - shift_tree_by(one->object.oid.hash, two->object.oid.hash, shifted.hash, + shift_tree_by(&one->object.oid, &two->object.oid, &shifted, subtree_shift); } if (!oidcmp(&two->object.oid, &shifted)) @@ -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" } @@ -446,7 +446,7 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, l = (struct leaf_node *) xcalloc(1, sizeof(struct leaf_node)); hashcpy(l->key_sha1, object_sha1); - hashcpy(l->val_sha1, entry.sha1); + hashcpy(l->val_sha1, entry.oid->hash); if (len < 20) { if (!S_ISDIR(entry.mode) || path_len != 2) goto handle_non_note; /* not subtree */ @@ -493,7 +493,7 @@ handle_non_note: } strbuf_addstr(&non_note_path, entry.path); add_non_note(t, strbuf_detach(&non_note_path, NULL), - entry.mode, entry.sha1); + entry.mode, entry.oid->hash); } } free(buf); @@ -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; @@ -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'); } } @@ -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; @@ -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 @@ -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; +} @@ -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-curl.c b/remote-curl.c index 15e48e25fb..672b382e5a 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -474,7 +474,7 @@ static int run_slot(struct active_request_slot *slot, static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) { struct active_request_slot *slot; - struct curl_slist *headers = NULL; + struct curl_slist *headers = http_copy_default_headers(); struct strbuf buf = STRBUF_INIT; int err; @@ -503,7 +503,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) static int post_rpc(struct rpc_state *rpc) { struct active_request_slot *slot; - struct curl_slist *headers = NULL; + struct curl_slist *headers = http_copy_default_headers(); int use_gzip = rpc->gzip_request; char *gzip_body = NULL; size_t gzip_size = 0; @@ -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) { @@ -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"); @@ -365,103 +515,6 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output } /* - * 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 * we are unable to handle, and return the determination in *type. @@ -569,6 +622,33 @@ int rerere_remaining(struct string_list *merge_rr) } /* + * 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 * "postimage" (i.e. the corresponding contents with conflicts @@ -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); @@ -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..d30d1c4f80 100644 --- a/revision.c +++ b/revision.c @@ -59,10 +59,10 @@ static void mark_tree_contents_uninteresting(struct tree *tree) while (tree_entry(&desc, &entry)) { switch (object_type(entry.mode)) { case OBJ_TREE: - mark_tree_uninteresting(lookup_tree(entry.sha1)); + mark_tree_uninteresting(lookup_tree(entry.oid->hash)); break; case OBJ_BLOB: - mark_blob_uninteresting(lookup_blob(entry.sha1)); + mark_blob_uninteresting(lookup_blob(entry.oid->hash)); break; default: /* Subproject commit - not in this repository */ @@ -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) @@ -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/sha1_name.c b/sha1_name.c index 776101e8d7..ca7ddd6f2c 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -1215,6 +1215,15 @@ int get_sha1(const char *name, unsigned char *sha1) } /* + * This is like "get_sha1()", but for struct object_id. + */ +int get_oid(const char *name, struct object_id *oid) +{ + return get_sha1(name, oid->hash); +} + + +/* * Many callers know that the user meant to name a commit-ish by * syntactical positions where the object name appears. Calling this * function allows the machinery to disambiguate shorter-than-unique @@ -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) @@ -387,6 +387,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 * this signature, but have different line termination conventions. 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 8476e0fa0f..debab294d4 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -30,7 +30,7 @@ enum lookup_type { lookup_path }; -static struct submodule_cache cache; +static struct submodule_cache the_submodule_cache; static int is_cache_init; static int config_path_cmp(const struct submodule_entry *a, @@ -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); @@ -457,14 +470,14 @@ static void ensure_cache_init(void) if (is_cache_init) return; - cache_init(&cache); + cache_init(&the_submodule_cache); is_cache_init = 1; } int parse_submodule_config_option(const char *var, const char *value) { struct parse_config_parameter parameter; - parameter.cache = &cache; + parameter.cache = &the_submodule_cache; parameter.commit_sha1 = NULL; parameter.gitmodules_sha1 = null_sha1; parameter.overwrite = 1; @@ -477,18 +490,18 @@ const struct submodule *submodule_from_name(const unsigned char *commit_sha1, const char *name) { ensure_cache_init(); - return config_from_name(&cache, commit_sha1, name); + return config_from_name(&the_submodule_cache, commit_sha1, name); } const struct submodule *submodule_from_path(const unsigned char *commit_sha1, const char *path) { ensure_cache_init(); - return config_from_path(&cache, commit_sha1, path); + return config_from_path(&the_submodule_cache, commit_sha1, path); } void submodule_free(void) { - cache_free(&cache); + cache_free(&the_submodule_cache); is_cache_init = 0; } 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/test-chmtime.c b/t/helper/test-chmtime.c index dfe8a83261..dfe8a83261 100644 --- a/test-chmtime.c +++ b/t/helper/test-chmtime.c diff --git a/test-config.c b/t/helper/test-config.c index 6a77552210..6a77552210 100644 --- a/test-config.c +++ b/t/helper/test-config.c diff --git a/test-ctype.c b/t/helper/test-ctype.c index 707a821f03..707a821f03 100644 --- a/test-ctype.c +++ b/t/helper/test-ctype.c diff --git a/test-date.c b/t/helper/test-date.c index 63f373557e..63f373557e 100644 --- a/test-date.c +++ b/t/helper/test-date.c diff --git a/test-delta.c b/t/helper/test-delta.c index 4595cd6433..4595cd6433 100644 --- a/test-delta.c +++ b/t/helper/test-delta.c diff --git a/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index bb53c0aa65..bb53c0aa65 100644 --- a/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c diff --git a/test-dump-split-index.c b/t/helper/test-dump-split-index.c index 861d28c9b6..861d28c9b6 100644 --- a/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c diff --git a/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index 0a1c285246..0a1c285246 100644 --- a/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c diff --git a/test-fake-ssh.c b/t/helper/test-fake-ssh.c index 980de216e1..980de216e1 100644 --- a/test-fake-ssh.c +++ b/t/helper/test-fake-ssh.c diff --git a/test-genrandom.c b/t/helper/test-genrandom.c index 54824d0754..54824d0754 100644 --- a/test-genrandom.c +++ b/t/helper/test-genrandom.c diff --git a/test-hashmap.c b/t/helper/test-hashmap.c index cc2891dd97..cc2891dd97 100644 --- a/test-hashmap.c +++ b/t/helper/test-hashmap.c diff --git a/test-index-version.c b/t/helper/test-index-version.c index 05d4699c4a..05d4699c4a 100644 --- a/test-index-version.c +++ b/t/helper/test-index-version.c diff --git a/test-line-buffer.c b/t/helper/test-line-buffer.c index 1e58f0476f..1e58f0476f 100644 --- a/test-line-buffer.c +++ b/t/helper/test-line-buffer.c diff --git a/test-match-trees.c b/t/helper/test-match-trees.c index 4dad7095f1..d446b8eaca 100644 --- a/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -3,24 +3,24 @@ int main(int ac, char **av) { - unsigned char hash1[20], hash2[20], shifted[20]; + struct object_id hash1, hash2, shifted; struct tree *one, *two; setup_git_directory(); - if (get_sha1(av[1], hash1)) + if (get_oid(av[1], &hash1)) die("cannot parse %s as an object name", av[1]); - if (get_sha1(av[2], hash2)) + if (get_oid(av[2], &hash2)) die("cannot parse %s as an object name", av[2]); - one = parse_tree_indirect(hash1); + one = parse_tree_indirect(hash1.hash); if (!one) die("not a tree-ish %s", av[1]); - two = parse_tree_indirect(hash2); + two = parse_tree_indirect(hash2.hash); 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)); + shift_tree(&one->object.oid, &two->object.oid, &shifted, -1); + printf("shifted: %s\n", oid_to_hex(&shifted)); exit(0); } diff --git a/test-mergesort.c b/t/helper/test-mergesort.c index ea3b959e94..ea3b959e94 100644 --- a/test-mergesort.c +++ b/t/helper/test-mergesort.c diff --git a/test-mktemp.c b/t/helper/test-mktemp.c index c8c54213a3..c8c54213a3 100644 --- a/test-mktemp.c +++ b/t/helper/test-mktemp.c diff --git a/test-parse-options.c b/t/helper/test-parse-options.c index 2c8c8f18ed..2c8c8f18ed 100644 --- a/test-parse-options.c +++ b/t/helper/test-parse-options.c diff --git a/test-path-utils.c b/t/helper/test-path-utils.c index ba805b374c..ba805b374c 100644 --- a/test-path-utils.c +++ b/t/helper/test-path-utils.c diff --git a/test-prio-queue.c b/t/helper/test-prio-queue.c index 7be72f0086..7be72f0086 100644 --- a/test-prio-queue.c +++ b/t/helper/test-prio-queue.c diff --git a/test-read-cache.c b/t/helper/test-read-cache.c index b25bcf139b..b25bcf139b 100644 --- a/test-read-cache.c +++ b/t/helper/test-read-cache.c diff --git a/test-regex.c b/t/helper/test-regex.c index 0dc598ecdc..0dc598ecdc 100644 --- a/test-regex.c +++ b/t/helper/test-regex.c diff --git a/test-revision-walking.c b/t/helper/test-revision-walking.c index 3d0313354b..3d0313354b 100644 --- a/test-revision-walking.c +++ b/t/helper/test-revision-walking.c diff --git a/test-run-command.c b/t/helper/test-run-command.c index 30a64a98dc..30a64a98dc 100644 --- a/test-run-command.c +++ b/t/helper/test-run-command.c diff --git a/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 6efee31a48..6efee31a48 100644 --- a/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c diff --git a/test-sha1-array.c b/t/helper/test-sha1-array.c index 60ea1d5f14..60ea1d5f14 100644 --- a/test-sha1-array.c +++ b/t/helper/test-sha1-array.c diff --git a/test-sha1.c b/t/helper/test-sha1.c index e57eae10bf..e57eae10bf 100644 --- a/test-sha1.c +++ b/t/helper/test-sha1.c diff --git a/test-sha1.sh b/t/helper/test-sha1.sh index cef4bcc866..750b95a0a1 100755 --- a/test-sha1.sh +++ b/t/helper/test-sha1.sh @@ -1,7 +1,7 @@ #!/bin/sh dd if=/dev/zero bs=1048576 count=100 2>/dev/null | -/usr/bin/time ./test-sha1 >/dev/null +/usr/bin/time t/helper/test-sha1 >/dev/null while read expect cnt pfx do @@ -11,7 +11,7 @@ do test -z "$pfx" || echo "$pfx" dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | perl -pe 'y/\000/g/' - } | ./test-sha1 $cnt + } | ./t/helper/test-sha1 $cnt ) if test "$expect" = "$actual" then diff --git a/test-sigchain.c b/t/helper/test-sigchain.c index e499fce60f..e499fce60f 100644 --- a/test-sigchain.c +++ b/t/helper/test-sigchain.c diff --git a/test-string-list.c b/t/helper/test-string-list.c index 14bdf9d215..14bdf9d215 100644 --- a/test-string-list.c +++ b/t/helper/test-string-list.c diff --git a/test-submodule-config.c b/t/helper/test-submodule-config.c index dab8c27768..dab8c27768 100644 --- a/test-submodule-config.c +++ b/t/helper/test-submodule-config.c diff --git a/test-subprocess.c b/t/helper/test-subprocess.c index 56881a0324..56881a0324 100644 --- a/test-subprocess.c +++ b/t/helper/test-subprocess.c diff --git a/test-svn-fe.c b/t/helper/test-svn-fe.c index 120ec96b0d..120ec96b0d 100644 --- a/test-svn-fe.c +++ b/t/helper/test-svn-fe.c diff --git a/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c index 090bf219a7..090bf219a7 100644 --- a/test-urlmatch-normalization.c +++ b/t/helper/test-urlmatch-normalization.c diff --git a/test-wildmatch.c b/t/helper/test-wildmatch.c index 578b164fe6..578b164fe6 100644 --- a/test-wildmatch.c +++ b/t/helper/test-wildmatch.c diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index f9ae1d780d..54fd5a6ca0 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -33,7 +33,7 @@ fi # Older versions of perforce were available compiled natively for # cygwin. Those do not accept native windows paths, so make sure # not to convert for them. -native_path() { +native_path () { path="$1" && if test_have_prereq CYGWIN && ! p4 -V | grep -q CYGWIN then @@ -49,8 +49,8 @@ native_path() { # Attention: This function is not safe again against time offset updates # at runtime (e.g. via NTP). The 'clock_gettime(CLOCK_MONOTONIC)' # function could fix that but it is not in Python until 3.3. -time_in_seconds() { - python -c 'import time; print int(time.time())' +time_in_seconds () { + (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } # Try to pick a unique port: guess a large number, then hope @@ -75,7 +75,7 @@ git="$TRASH_DIRECTORY/git" pidfile="$TRASH_DIRECTORY/p4d.pid" # Sometimes "prove" seems to hang on exit because p4d is still running -cleanup() { +cleanup () { if test -f "$pidfile" then kill -9 $(cat "$pidfile") 2>/dev/null && exit 255 @@ -89,7 +89,7 @@ trap cleanup EXIT TMPDIR="$TRASH_DIRECTORY" export TMPDIR -start_p4d() { +start_p4d () { mkdir -p "$db" "$cli" "$git" && rm -f "$pidfile" && ( @@ -151,7 +151,7 @@ start_p4d() { return 0 } -p4_add_user() { +p4_add_user () { name=$1 && p4 user -f -i <<-EOF User: $name @@ -160,7 +160,16 @@ p4_add_user() { EOF } -retry_until_success() { +p4_add_job () { + p4 job -f -i <<-EOF + Job: $1 + Status: open + User: dummy + Description: + EOF +} + +retry_until_success () { timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) until "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout do @@ -168,7 +177,7 @@ retry_until_success() { done } -retry_until_fail() { +retry_until_fail () { timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) until ! "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout do @@ -176,7 +185,7 @@ retry_until_fail() { done } -kill_p4d() { +kill_p4d () { pid=$(cat "$pidfile") retry_until_fail kill $pid retry_until_fail kill -9 $pid @@ -186,21 +195,22 @@ kill_p4d() { retry_until_fail kill -9 $watchdog_pid } -cleanup_git() { +cleanup_git () { retry_until_success rm -r "$git" test_must_fail test -d "$git" && retry_until_success mkdir "$git" } -marshal_dump() { +marshal_dump () { what=$1 && line=${2:-1} && cat >"$TRASH_DIRECTORY/marshal-dump.py" <<-EOF && import marshal import sys + instream = getattr(sys.stdin, 'buffer', sys.stdin) for i in range($line): - d = marshal.load(sys.stdin) - print d['$what'] + d = marshal.load(instream) + print(d[b'$what'].decode('utf-8')) EOF "$PYTHON_PATH" "$TRASH_DIRECTORY/marshal-dump.py" } @@ -208,7 +218,7 @@ marshal_dump() { # # Construct a client with this list of View lines # -client_view() { +client_view () { ( cat <<-EOF && Client: $P4CLIENT @@ -222,7 +232,7 @@ client_view() { ) | p4 client -i } -is_cli_file_writeable() { +is_cli_file_writeable () { # cygwin version of p4 does not set read-only attr, # will be marked 444 but -w is true file="$1" && diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index db2ef22e8f..ec2aa8f687 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -1,9 +1,8 @@ #!/bin/sh gpg_version=$(gpg --version 2>&1) -if test $? = 127; then - say "You do not seem to have gpg installed" -else +if test $? != 127 +then # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 # the gpg version 1.0.6 didn't parse trust packets correctly, so for # that version, creation of signed tags using the generated key fails. diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 9317ba0858..b8ed96fac6 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -102,6 +102,14 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_HTTP_EXPORT_ALL Header set Set-Cookie name=value </LocationMatch> +<LocationMatch /smart_headers/> + <RequireAll> + Require expr %{HTTP:x-magic-one} == 'abra' + Require expr %{HTTP:x-magic-two} == 'cadabra' + </RequireAll> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error/ error.sh/ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index d7ef44b4a2..03bd31e9f2 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -298,4 +298,15 @@ test_expect_success 'helpers can abort the process' ' test_cmp expect stdout ' +test_expect_success 'empty helper spec resets helper list' ' + test_config credential.helper "verbatim file file" && + check fill "" "verbatim cmdline cmdline" <<-\EOF + -- + username=cmdline + password=cmdline + -- + verbatim: get + EOF +' + test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 8e22b03cdd..df3183ea1a 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -141,13 +141,13 @@ test_expect_success 'GIT_PREFIX for !alias' ' test_expect_success 'GIT_PREFIX for built-ins' ' # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in # receives the GIT_PREFIX variable. - printf "dir/" >expect && - printf "#!/bin/sh\n" >diff && - printf "printf \"\$GIT_PREFIX\"" >>diff && - chmod +x diff && + echo "dir/" >expect && + write_script diff <<-\EOF && + printf "%s\n" "$GIT_PREFIX" + EOF ( cd dir && - printf "change" >two && + echo "change" >two && GIT_EXTERNAL_DIFF=./diff git diff >../actual git checkout -- two ) && diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3d6f1db9da..d934a24417 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1087,6 +1087,20 @@ test_expect_success 'git -c complains about empty key and value' ' test_must_fail git -c "" rev-parse ' +test_expect_success 'multiple git -c appends config' ' + test_config alias.x "!git -c x.two=2 config --get-regexp ^x\.*" && + cat >expect <<-\EOF && + x.one 1 + x.two 2 + EOF + git -c x.one=1 x >actual && + test_cmp expect actual +' + +test_expect_success 'git -c is not confused by empty environment' ' + GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list +' + test_expect_success 'git config --edit works' ' git config -f tmp test.value no && echo test.value=yes >expect && diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index c623824b4d..9cf91dc6d2 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -338,4 +338,14 @@ test_expect_failure 'reflog with non-commit entries displays all entries' ' test_line_count = 3 actual ' +test_expect_success 'reflog expire operates on symref not referrent' ' + git branch -l the_symref && + git branch -l referrent && + git update-ref referrent HEAD && + git symbolic-ref refs/heads/the_symref refs/heads/referrent && + test_when_finished "rm -f .git/refs/heads/referrent.lock" && + touch .git/refs/heads/referrent.lock && + git reflog expire --expire=all the_symref +' + test_done diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index c465abe8e3..25ddab4e98 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -42,7 +42,7 @@ test_expect_success 'git branch shows badly named ref as warning' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch >output 2>error && - grep -e "broken\.\.\.ref" error && + test_i18ngrep -e "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -147,35 +147,145 @@ test_expect_success 'rev-parse skips symref pointing to broken name' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch shadow one && cp .git/refs/heads/master .git/refs/heads/broken...ref && - git symbolic-ref refs/tags/shadow refs/heads/broken...ref && - + printf "ref: refs/heads/broken...ref\n" >.git/refs/tags/shadow && + test_when_finished "rm -f .git/refs/tags/shadow" && git rev-parse --verify one >expect && git rev-parse --verify shadow >actual 2>err && test_cmp expect actual && - test_i18ngrep "ignoring.*refs/tags/shadow" err + test_i18ngrep "ignoring dangling symref refs/tags/shadow" err ' -test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' - git symbolic-ref refs/heads/badname refs/heads/broken...ref && +test_expect_success 'for-each-ref emits warnings for broken names' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && test_when_finished "rm -f .git/refs/heads/badname" && - test_path_is_file .git/refs/heads/badname && - git update-ref --no-deref -d refs/heads/badname && - test_path_is_missing .git/refs/heads/badname + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git for-each-ref >output 2>error && + ! grep -e "broken\.\.\.ref" output && + ! grep -e "badname" output && + ! grep -e "broken\.\.\.symref" output && + test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && + test_i18ngrep "ignoring broken ref refs/heads/badname" error && + test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error ' test_expect_success 'update-ref -d can delete broken name' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && - git update-ref -d refs/heads/broken...ref && + git update-ref -d refs/heads/broken...ref >output 2>error && + test_must_be_empty output && + test_must_be_empty error && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'branch -d can delete broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -d broken...ref >output 2>error && + test_i18ngrep "Deleted branch broken...ref (was broken)" output && + test_must_be_empty error && git branch >output 2>error && ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' +test_expect_success 'update-ref --no-deref -d can delete symref to broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref --no-deref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete symref to broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git branch -d badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' ' + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref --no-deref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete dangling symref to broken name' ' + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git branch -d badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref -d can delete broken name through symref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/broken...ref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete symref with broken name' ' + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete symref with broken name' ' + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git branch -d broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_i18ngrep "Deleted branch broken...symref (was refs/heads/master)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' ' + printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete dangling symref with broken name' ' + printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git branch -d broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output && + test_must_be_empty error +' + test_expect_success 'update-ref -d cannot delete non-ref in .git dir' ' echo precious >.git/my-private-file && echo precious >expect && - test_must_fail git update-ref -d my-private-file && + test_must_fail git update-ref -d my-private-file >output 2>error && + test_must_be_empty output && + test_i18ngrep -e "cannot lock .*: unable to resolve reference" error && test_cmp expect .git/my-private-file ' diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index cbfa41ec61..3acb9926f2 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -213,4 +213,16 @@ test_expect_success 'local clone from linked checkout' ' ( cd here-clone && git fsck ) ' +test_expect_success '"add" worktree with --no-checkout' ' + git worktree add --no-checkout -b swamp swamp && + ! test -e swamp/init.t && + git -C swamp reset --hard && + test_cmp init.t swamp/init.t +' + +test_expect_success '"add" worktree with --checkout' ' + git worktree add --checkout -b swmap2 swamp2 && + test_cmp init.t swamp2/init.t +' + test_done diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh index 46aadc410b..d314599428 100755 --- a/t/t3033-merge-toplevel.sh +++ b/t/t3033-merge-toplevel.sh @@ -19,6 +19,8 @@ test_expect_success setup ' test_commit three && git checkout right && test_commit four && + git checkout --orphan newroot && + test_commit five && git checkout master ' @@ -133,4 +135,18 @@ test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' ' test_cmp expect actual ' +# two-project merge +test_expect_success 'refuse two-project merge by default' ' + t3033_reset && + git reset --hard four && + test_must_fail git merge five +' + +test_expect_success 'two-project merge with --allow-unrelated-histories' ' + t3033_reset && + git reset --hard four && + git merge --allow-unrelated-histories five && + git diff --exit-code five +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index a897248490..f3e3b6cf2e 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -126,7 +126,28 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success 'git branch -M baz bam should succeed when baz is checked out' ' git checkout -b baz && git branch bam && - git branch -M baz bam + git branch -M baz bam && + test $(git rev-parse --abbrev-ref HEAD) = bam +' + +test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' ' + git checkout master && + git worktree add -b baz bazdir && + git worktree add -f bazdir2 baz && + git branch -M baz bam && + test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam && + test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam +' + +test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' ' + git checkout -b baz && + git worktree add -f bazdir3 baz && + ( + cd bazdir3 && + git branch -M baz bam && + test $(git rev-parse --abbrev-ref HEAD) = bam + ) && + test $(git rev-parse --abbrev-ref HEAD) = bam ' test_expect_success 'git branch -M master should work when master is checked out' ' @@ -403,6 +424,12 @@ test_expect_success 'test deleting branch without config' ' test_i18ncmp expect actual ' +test_expect_success 'deleting currently checked out branch fails' ' + git worktree add -b my7 my7 && + test_must_fail git -C my7 branch -d my7 && + test_must_fail git branch -d my7 +' + test_expect_success 'test --track without .fetch entries' ' git branch --track my8 && test "$(git config branch.my8.remote)" && diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 4261403cf6..c6a3ccba1b 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -184,4 +184,16 @@ test_expect_success 'ambiguous branch/tag not marked' ' test_cmp expect actual ' +test_expect_success 'local-branch symrefs shortened properly' ' + git symbolic-ref refs/heads/ref-to-branch refs/heads/branch-one && + git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one && + cat >expect <<-\EOF && + ref-to-branch -> branch-one + ref-to-remote -> refs/remotes/origin/branch-one + EOF + git branch >actual.raw && + grep ref-to <actual.raw >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 8f64505e4f..488945e007 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -85,6 +85,15 @@ test_expect_success 'rebase -Xtheirs' ' ! grep 11 original ' +test_expect_success 'rebase -Xtheirs from orphan' ' + git checkout --orphan orphan-conflicting master~2 && + echo "AB $T" >> original && + git commit -morphan-conflicting original && + git rebase -Xtheirs master && + grep AB original && + ! grep 11 original +' + test_expect_success 'merge and rebase should match' ' git diff-tree -r test-rebase test-merge >difference && if test -s difference diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 544f9ad508..d96d0e4c9b 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -555,10 +555,9 @@ test_expect_success 'rebase a detached HEAD' ' test_expect_success 'rebase a commit violating pre-commit' ' mkdir -p .git/hooks && - PRE_COMMIT=.git/hooks/pre-commit && - echo "#!/bin/sh" > $PRE_COMMIT && - echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT && - chmod a+x $PRE_COMMIT && + write_script .git/hooks/pre-commit <<-\EOF && + test -z "$(git diff --cached --check)" + EOF echo "monde! " >> file1 && test_tick && test_must_fail git commit -m doesnt-verify file1 && @@ -771,7 +770,6 @@ test_expect_success 'rebase-i history with funny messages' ' test_cmp expect actual ' - test_expect_success 'prepare for rebase -i --exec' ' git checkout master && git checkout -b execute && @@ -780,7 +778,6 @@ test_expect_success 'prepare for rebase -i --exec' ' test_commit three_exec main.txt three_exec ' - test_expect_success 'running "git rebase -i --exec git show HEAD"' ' set_fake_editor && git rebase -i --exec "git show HEAD" HEAD~2 >actual && @@ -793,7 +790,6 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase --exec git show HEAD -i"' ' git reset --hard execute && set_fake_editor && @@ -807,7 +803,6 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase -ix git show HEAD"' ' git reset --hard execute && set_fake_editor && @@ -835,7 +830,6 @@ test_expect_success 'rebase -ix with several <CMD>' ' test_cmp expected actual ' - test_expect_success 'rebase -ix with several instances of --exec' ' git reset --hard execute && set_fake_editor && @@ -850,7 +844,6 @@ test_expect_success 'rebase -ix with several instances of --exec' ' test_cmp expected actual ' - test_expect_success 'rebase -ix with --autosquash' ' git reset --hard execute && git checkout -b autosquash && @@ -876,16 +869,15 @@ test_expect_success 'rebase -ix with --autosquash' ' test_cmp expected actual ' - -test_expect_success 'rebase --exec without -i shows error message' ' +test_expect_success 'rebase --exec works without -i ' ' git reset --hard execute && - set_fake_editor && - test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual && - echo "The --exec option must be used with the --interactive option" >expected && - test_i18ncmp expected actual + rm -rf exec_output && + EDITOR="echo >invoked_editor" git rebase --exec "echo a line >>exec_output" HEAD~2 2>actual && + test_i18ngrep "Successfully rebased and updated" actual && + test_line_count = 2 exec_output && + test_path_is_missing invoked_editor ' - test_expect_success 'rebase -i --exec without <CMD>' ' git reset --hard execute && set_fake_editor && diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 0b52105728..73a39f2923 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -133,7 +133,7 @@ test_expect_success 'set up second root and merge' ' rm A B C && test_commit 6 D && git checkout other && - git merge third + git merge --allow-unrelated-histories third ' cat > expect-third <<'EOF' diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 9c55cba198..68fe2003ef 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -253,7 +253,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase success -p @@ -268,7 +268,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase failure -p diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index ed90c6c6f9..0d1fa45d25 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -9,21 +9,84 @@ test_description='Test rename detection in diff engine. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh -echo >path0 'Line 1 -Line 2 -Line 3 -Line 4 -Line 5 -Line 6 -Line 7 -Line 8 -Line 9 -Line 10 -line 11 -Line 12 -Line 13 -Line 14 -Line 15 +test_expect_success 'setup' ' + cat >path0 <<-\EOF && + Line 1 + Line 2 + Line 3 + Line 4 + Line 5 + Line 6 + Line 7 + Line 8 + Line 9 + Line 10 + line 11 + Line 12 + Line 13 + Line 14 + Line 15 + EOF + cat >expected <<-\EOF && + diff --git a/path0 b/path1 + rename from path0 + rename to path1 + --- a/path0 + +++ b/path1 + @@ -8,7 +8,7 @@ Line 7 + Line 8 + Line 9 + Line 10 + -line 11 + +Line 11 + Line 12 + Line 13 + Line 14 + EOF + cat >no-rename <<-\EOF + diff --git a/path0 b/path0 + deleted file mode 100644 + index fdbec44..0000000 + --- a/path0 + +++ /dev/null + @@ -1,15 +0,0 @@ + -Line 1 + -Line 2 + -Line 3 + -Line 4 + -Line 5 + -Line 6 + -Line 7 + -Line 8 + -Line 9 + -Line 10 + -line 11 + -Line 12 + -Line 13 + -Line 14 + -Line 15 + diff --git a/path1 b/path1 + new file mode 100644 + index 0000000..752c50e + --- /dev/null + +++ b/path1 + @@ -0,0 +1,15 @@ + +Line 1 + +Line 2 + +Line 3 + +Line 4 + +Line 5 + +Line 6 + +Line 7 + +Line 8 + +Line 9 + +Line 10 + +Line 11 + +Line 12 + +Line 13 + +Line 14 + +Line 15 + EOF ' test_expect_success \ @@ -43,27 +106,27 @@ test_expect_success \ test_expect_success \ 'git diff-index -p -M after rename and editing.' \ 'git diff-index -p -M $tree >current' -cat >expected <<\EOF -diff --git a/path0 b/path1 -rename from path0 -rename to path1 ---- a/path0 -+++ b/path1 -@@ -8,7 +8,7 @@ Line 7 - Line 8 - Line 9 - Line 10 --line 11 -+Line 11 - Line 12 - Line 13 - Line 14 -EOF + test_expect_success \ 'validate the output.' \ 'compare_diff_patch current expected' +test_expect_success 'test diff.renames=true' ' + git -c diff.renames=true diff --cached $tree >current && + compare_diff_patch current expected +' + +test_expect_success 'test diff.renames=false' ' + git -c diff.renames=false diff --cached $tree >current && + compare_diff_patch current no-rename +' + +test_expect_success 'test diff.renames unset' ' + git diff --cached $tree >current && + compare_diff_patch current expected +' + test_expect_success 'favour same basenames over different ones' ' cp path1 another-path && git add another-path && diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 6ec6072118..94ef5000e7 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -90,6 +90,8 @@ test_expect_success setup ' git commit -m "Rearranged lines in dir/sub" && git checkout master && + git config diff.renames false && + git show-branch ' diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 3b99434e3e..eed2981b96 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -549,7 +549,7 @@ test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && - git format-patch --cover-letter -1 && + git format-patch --no-renames --cover-letter -1 && check_patch 0000-cover-letter.patch && ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch && git format-patch --cover-letter -1 -M && @@ -703,7 +703,7 @@ test_expect_success 'options no longer allowed for format-patch' ' test_expect_success 'format-patch --numstat should produce a patch' ' git format-patch --numstat --stdout master..side > output && - test 6 = $(grep "^diff --git a/" output | wc -l)' + test 5 = $(grep "^diff --git a/" output | wc -l)' test_expect_success 'format-patch -- <path>' ' git format-patch master..side -- file 2>error && diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh index 3b8b7921d6..447a8ffa3a 100755 --- a/t/t4047-diff-dirstat.sh +++ b/t/t4047-diff-dirstat.sh @@ -248,7 +248,8 @@ EOF git rm -r src/move/unchanged && git rm -r src/move/changed && git rm -r src/move/rearranged && - git commit -m "changes" + git commit -m "changes" && + git config diff.renames false ' cat <<EOF >expect_diff_stat diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index ed9c91e25b..1a080e7823 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -184,12 +184,27 @@ test_expect_success 'rerere updates postimage timestamp' ' ' test_expect_success 'rerere clear' ' - rm $rr/postimage && + mv $rr/postimage .git/post-saved && echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR && git rerere clear && ! test -d $rr ' +test_expect_success 'leftover directory' ' + git reset --hard && + mkdir -p $rr && + test_must_fail git merge first && + test -f $rr/preimage +' + +test_expect_success 'missing preimage' ' + git reset --hard && + mkdir -p $rr && + cp .git/post-saved $rr/postimage && + test_must_fail git merge first && + test -f $rr/preimage +' + test_expect_success 'set up for garbage collection tests' ' mkdir -p $rr && echo Hello >$rr/preimage && @@ -391,4 +406,157 @@ test_expect_success 'rerere -h' ' test_i18ngrep [Uu]sage help ' +concat_insert () { + last=$1 + shift + cat early && printf "%s\n" "$@" && cat late "$last" +} + +count_pre_post () { + find .git/rr-cache/ -type f -name "preimage*" >actual && + test_line_count = "$1" actual && + find .git/rr-cache/ -type f -name "postimage*" >actual && + test_line_count = "$2" actual +} + +test_expect_success 'rerere gc' ' + find .git/rr-cache -type f >original && + xargs test-chmtime -172800 <original && + + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && + find .git/rr-cache -type f >actual && + test_cmp original actual && + + git -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc && + find .git/rr-cache -type f >actual && + test_cmp original actual && + + git -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc && + find .git/rr-cache -type f >actual && + >expect && + test_cmp expect actual +' + +merge_conflict_resolve () { + git reset --hard && + test_must_fail git merge six.1 && + # Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both) + concat_insert short 6.1 6.2 >file1 && + concat_insert long 6.1 6.2 >file2 +} + +test_expect_success 'multiple identical conflicts' ' + git reset --hard && + + test_seq 1 6 >early && + >late && + test_seq 11 15 >short && + test_seq 111 120 >long && + concat_insert short >file1 && + concat_insert long >file2 && + git add file1 file2 && + git commit -m base && + git tag base && + git checkout -b six.1 && + concat_insert short 6.1 >file1 && + concat_insert long 6.1 >file2 && + git add file1 file2 && + git commit -m 6.1 && + git checkout -b six.2 HEAD^ && + concat_insert short 6.2 >file1 && + concat_insert long 6.2 >file2 && + git add file1 file2 && + git commit -m 6.2 && + + # At this point, six.1 and six.2 + # - derive from common ancestor that has two files + # 1...6 7 11..15 (file1) and 1...6 7 111..120 (file2) + # - six.1 replaces these 7s with 6.1 + # - six.2 replaces these 7s with 6.2 + + merge_conflict_resolve && + + # Check that rerere knows that file1 and file2 have conflicts + + printf "%s\n" file1 file2 >expect && + git ls-files -u | sed -e "s/^.* //" | sort -u >actual && + test_cmp expect actual && + + git rerere status | sort >actual && + test_cmp expect actual && + + git rerere remaining >actual && + test_cmp expect actual && + + count_pre_post 2 0 && + + # Pretend that the conflicts were made quite some time ago + find .git/rr-cache/ -type f | xargs test-chmtime -172800 && + + # Unresolved entries have not expired yet + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && + count_pre_post 2 0 && + + # Unresolved entries have expired + git -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc && + count_pre_post 0 0 && + + # Recreate the conflicted state + merge_conflict_resolve && + count_pre_post 2 0 && + + # Clear it + git rerere clear && + count_pre_post 0 0 && + + # Recreate the conflicted state + merge_conflict_resolve && + count_pre_post 2 0 && + + # We resolved file1 and file2 + git rerere && + >expect && + git rerere remaining >actual && + test_cmp expect actual && + + # We must have recorded both of them + count_pre_post 2 2 && + + # Now we should be able to resolve them both + git reset --hard && + test_must_fail git merge six.1 && + git rerere && + + >expect && + git rerere remaining >actual && + test_cmp expect actual && + + concat_insert short 6.1 6.2 >file1.expect && + concat_insert long 6.1 6.2 >file2.expect && + test_cmp file1.expect file1 && + test_cmp file2.expect file2 && + + # Forget resolution for file2 + git rerere forget file2 && + echo file2 >expect && + git rerere status >actual && + test_cmp expect actual && + count_pre_post 2 1 && + + # file2 already has correct resolution, so record it again + git rerere && + + # Pretend that the resolutions are old again + find .git/rr-cache/ -type f | xargs test-chmtime -172800 && + + # Resolved entries have not expired yet + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && + + count_pre_post 2 2 && + + # Resolved entries have expired + git -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc && + count_pre_post 0 0 +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index f5e63670fa..a9773658f0 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -115,7 +115,7 @@ EOF ' test_expect_success !MINGW 'shortlog from non-git directory' ' - git log HEAD >log && + git log --no-expand-tabs HEAD >log && GIT_DIR=non-existing git shortlog -w <log >out && test_cmp expect out ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index cb82eb7e66..128ba93537 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -101,8 +101,8 @@ test_expect_success 'oneline' ' test_expect_success 'diff-filter=A' ' - git log --pretty="format:%s" --diff-filter=A HEAD > actual && - git log --pretty="format:%s" --diff-filter A HEAD > actual-separate && + git log --no-renames --pretty="format:%s" --diff-filter=A HEAD > actual && + git log --no-renames --pretty="format:%s" --diff-filter A HEAD > actual-separate && printf "fifth\nfourth\nthird\ninitial" > expect && test_cmp expect actual && test_cmp expect actual-separate @@ -119,7 +119,7 @@ test_expect_success 'diff-filter=M' ' test_expect_success 'diff-filter=D' ' - actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) && + actual=$(git log --no-renames --pretty="format:%s" --diff-filter=D HEAD) && expect=$(echo sixth ; echo third) && verbose test "$actual" = "$expect" @@ -848,7 +848,7 @@ sanitize_output () { } test_expect_success 'log --graph with diff and stats' ' - git log --graph --pretty=short --stat -p >actual && + git log --no-renames --graph --pretty=short --stat -p >actual && sanitize_output >actual.sanitized <actual && test_i18ncmp expect actual.sanitized ' diff --git a/t/t4213-log-tabexpand.sh b/t/t4213-log-tabexpand.sh new file mode 100755 index 0000000000..e01a8f6ac9 --- /dev/null +++ b/t/t4213-log-tabexpand.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='log/show --expand-tabs' + +. ./test-lib.sh + +HT=" " +title='tab indent at the beginning of the title line' +body='tab indent on a line in the body' + +# usage: count_expand $indent $numSP $numHT @format_args +count_expand () +{ + expect= + count=$(( $1 + $2 )) ;# expected spaces + while test $count -gt 0 + do + expect="$expect " + count=$(( $count - 1 )) + done + shift 2 + count=$1 ;# expected tabs + while test $count -gt 0 + do + expect="$expect$HT" + count=$(( $count - 1 )) + done + shift + + # The remainder of the command line is "git show -s" options + case " $* " in + *' --pretty=short '*) + line=$title ;; + *) + line=$body ;; + esac + + # Prefix the output with the command line arguments, and + # replace SP with a dot both in the expecte and actual output + # so that test_cmp would show the differene together with the + # breakage in a way easier to consume by the debugging user. + { + echo "git show -s $*" + echo "$expect$line" + } | sed -e 's/ /./g' >expect + + { + echo "git show -s $*" + git show -s "$@" | + sed -n -e "/$line\$/p" + } | sed -e 's/ /./g' >actual + + test_cmp expect actual +} + +test_expand () +{ + fmt=$1 + case "$fmt" in + *=raw | *=short | *=email) + default="0 1" ;; + *) + default="8 0" ;; + esac + case "$fmt" in + *=email) + in=0 ;; + *) + in=4 ;; + esac + test_expect_success "expand/no-expand${fmt:+ for $fmt}" ' + count_expand $in $default $fmt && + count_expand $in 8 0 $fmt --expand-tabs && + count_expand $in 8 0 --expand-tabs $fmt && + count_expand $in 8 0 $fmt --expand-tabs=8 && + count_expand $in 8 0 --expand-tabs=8 $fmt && + count_expand $in 0 1 $fmt --no-expand-tabs && + count_expand $in 0 1 --no-expand-tabs $fmt && + count_expand $in 0 1 $fmt --expand-tabs=0 && + count_expand $in 0 1 --expand-tabs=0 $fmt && + count_expand $in 4 0 $fmt --expand-tabs=4 && + count_expand $in 4 0 --expand-tabs=4 $fmt + ' +} + +test_expect_success 'setup' ' + test_tick && + sed -e "s/Q/$HT/g" <<-EOF >msg && + Q$title + + Q$body + EOF + git commit --allow-empty -F msg +' + +test_expand "" +test_expand --pretty +test_expand --pretty=short +test_expand --pretty=medium +test_expand --pretty=full +test_expand --pretty=fuller +test_expand --pretty=raw +test_expand --pretty=email + +test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 9b9bec468a..91a69fc33a 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -259,7 +259,8 @@ test_expect_success 'clone shallow object count' ' test_expect_success 'pull in shallow repo with missing merge base' ' ( cd shallow && - test_must_fail git pull --depth 4 .. A + git fetch --depth 4 .. A + test_must_fail git merge --allow-unrelated-histories FETCH_HEAD ) ' @@ -279,9 +280,10 @@ test_expect_success 'clone shallow depth count' ' test_expect_success 'clone shallow object count' ' ( cd shallow && + git prune && git count-objects -v ) > count.shallow && - grep "^count: 55" count.shallow + grep "^count: 54" count.shallow ' test_expect_success 'fetch --no-shallow on full repo' ' diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index a3e12d295a..44f3d5fb28 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -100,11 +100,8 @@ test_expect_success 'push with receive.fsckobjects' ' git config receive.fsckobjects true && git config transfer.fsckobjects false ) && - test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act && - { - test_cmp exp act || - ! test -s act - } + test_must_fail git push --porcelain dst master:refs/heads/test >act && + test_cmp exp act ' test_expect_success 'push with transfer.fsckobjects' ' @@ -114,7 +111,8 @@ test_expect_success 'push with transfer.fsckobjects' ' cd dst && git config transfer.fsckobjects true ) && - test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act + test_must_fail git push --porcelain dst master:refs/heads/test >act && + test_cmp exp act ' cat >bogus-commit <<\EOF diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index c952d5ef5c..739c089d50 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -9,6 +9,24 @@ modify () { mv "$2.x" "$2" } +test_pull_autostash () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + git pull "$@" . copy && + test_cmp_rev HEAD^ copy && + test "$(cat new_file)" = dirty && + test "$(cat file)" = "modified again" +} + +test_pull_autostash_fail () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + test_must_fail git pull "$@" . copy 2>err && + test_i18ngrep "uncommitted changes." err +} + test_expect_success setup ' echo file >file && git add file && @@ -247,15 +265,47 @@ test_expect_success '--rebase fails with multiple branches' ' test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' ' test_config rebase.autostash true && - git reset --hard before-rebase && - echo dirty >new_file && - git add new_file && - git pull --rebase . copy && - test_cmp_rev HEAD^ copy && - test "$(cat new_file)" = dirty && - test "$(cat file)" = "modified again" + test_pull_autostash --rebase ' +test_expect_success 'pull --rebase --autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash_fail --rebase --no-autostash +' + +for i in --autostash --no-autostash +do + test_expect_success "pull $i (without --rebase) is illegal" ' + test_must_fail git pull $i . copy 2>err && + test_i18ngrep "only valid with --rebase" err + ' +done + test_expect_success 'pull.rebase' ' git reset --hard before-rebase && test_config pull.rebase true && @@ -264,6 +314,16 @@ test_expect_success 'pull.rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success 'pull --autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash --autostash +' + +test_expect_success 'pull --no-autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash_fail --no-autostash +' + test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && test_config branch.to-rebase.rebase true && diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index 18372caa15..ded8f98dbe 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -144,4 +144,25 @@ test_expect_success 'git pull --all --dry-run' ' ) ' +test_expect_success 'git pull --allow-unrelated-histories' ' + test_when_finished "rm -fr src dst" && + git init src && + ( + cd src && + test_commit one && + test_commit two + ) && + git clone src dst && + ( + cd src && + git checkout --orphan side HEAD^ && + test_commit three + ) && + ( + cd dst && + test_must_fail git pull ../src side && + git pull --allow-unrelated-histories ../src side + ) +' + test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 1241146227..954d0e43f5 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -471,4 +471,18 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea test_i18ncmp expect.err actual.err ' +test_expect_success 'fetching submodules respects parallel settings' ' + git config fetch.recurseSubmodules true && + ( + cd downstream && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git fetch && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 9 && + grep "9 tasks" trace.out + ) +' + test_done diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh index d75ef0ea2b..51c9669398 100755 --- a/t/t5532-fetch-proxy.sh +++ b/t/t5532-fetch-proxy.sh @@ -12,10 +12,8 @@ test_expect_success 'setup remote repo' ' ) ' -cat >proxy <<'EOF' -#!/bin/sh -echo >&2 "proxying for $*" -cmd=$("$PERL_PATH" -e ' +test_expect_success 'setup proxy script' ' + write_script proxy-get-cmd "$PERL_PATH" <<-\EOF && read(STDIN, $buf, 4); my $n = hex($buf) - 4; read(STDIN, $buf, $n); @@ -23,11 +21,16 @@ cmd=$("$PERL_PATH" -e ' # drop absolute-path on repo name $cmd =~ s{ /}{ }; print $cmd; -') -echo >&2 "Running '$cmd'" -exec $cmd -EOF -chmod +x proxy + EOF + + write_script proxy <<-\EOF + echo >&2 "proxying for $*" + cmd=$(./proxy-get-cmd) + echo >&2 "Running $cmd" + exec $cmd + EOF +' + test_expect_success 'setup local repo' ' git remote add fake git://example.com/remote && git config core.gitproxy ./proxy diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 64146352ae..48e2ab62da 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -91,6 +91,23 @@ test_expect_success 'configured username does not override URL' ' expect_askpass pass user@host ' +test_expect_success 'cmdline credential config passes into submodules' ' + git init super && + set_askpass user@host pass@host && + ( + cd super && + git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub && + git commit -m "add submodule" + ) && + set_askpass wrong pass@host && + test_must_fail git clone --recursive super super-clone && + rm -rf super-clone && + set_askpass wrong pass@host && + git -c "credential.$HTTP_URL.username=user@host" \ + clone --recursive super super-clone && + expect_askpass pass user@host +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two && diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 58207d8825..e44fe72c7a 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -282,5 +282,12 @@ test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' ' test_line_count = 100000 tags ' +test_expect_success 'custom http headers' ' + test_must_fail git fetch "$HTTPD_URL/smart_headers/repo.git" && + git -c http.extraheader="x-magic-one: abra" \ + -c http.extraheader="x-magic-two: cadabra" \ + fetch "$HTTPD_URL/smart_headers/repo.git" +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index c1efb8e445..150aeaf713 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -308,7 +308,7 @@ test_expect_success 'clone checking out a tag' ' setup_ssh_wrapper () { test_expect_success 'setup ssh wrapper' ' - cp "$GIT_BUILD_DIR/test-fake-ssh$X" \ + cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \ "$TRASH_DIRECTORY/ssh-wrapper$X" && GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" && export GIT_SSH && diff --git a/t/t5614-clone-submodules.sh b/t/t5614-clone-submodules.sh new file mode 100755 index 0000000000..62044c5a02 --- /dev/null +++ b/t/t5614-clone-submodules.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +test_description='Test shallow cloning of repos with submodules' + +. ./test-lib.sh + +pwd=$(pwd) + +test_expect_success 'setup' ' + git checkout -b master && + test_commit commit1 && + test_commit commit2 && + mkdir sub && + ( + cd sub && + git init && + test_commit subcommit1 && + test_commit subcommit2 && + test_commit subcommit3 + ) && + git submodule add "file://$pwd/sub" sub && + git commit -m "add submodule" +' + +test_expect_success 'nonshallow clone implies nonshallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 3 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 3 lines + ) +' + +test_expect_success 'shallow clone implies shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --depth 2 "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 2 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 1 lines + ) +' + +test_expect_success 'shallow clone with non shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --depth 2 --no-shallow-submodules "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 2 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 3 lines + ) +' + +test_expect_success 'non shallow clone with shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --no-local --shallow-submodules "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 3 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 1 lines + ) +' + +test_done diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 66cda17ef3..20e3e2554a 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -47,7 +47,9 @@ test_expect_success 'setup roots, merges and octopuses' ' git checkout -b yetanotherbranch four && test_commit eight && git checkout master && - test_merge normalmerge newroot && + test_tick && + git merge --allow-unrelated-histories -m normalmerge newroot && + git tag normalmerge && test_tick && git merge -m tripus sidebranch anotherbranch && git tag tripus && diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 39b3238da2..e0c5f44cac 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -215,11 +215,13 @@ test_expect_success 'criss-cross merge-base for octopus-step' ' git reset --hard E && test_commit CC2 && test_tick && - git merge -s ours CC1 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC1 && test_commit CC-o && test_commit CCB && git reset --hard CC1 && - git merge -s ours CC2 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC2 && test_commit CCA && git rev-parse CC1 CC2 >expected && diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index b89cd6b07a..2a0fbb87b1 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -71,7 +71,7 @@ test_expect_success setup ' note J && git checkout master && - test_tick && git merge -m "Coolest" unrelated && + test_tick && git merge --allow-unrelated-histories -m "Coolest" unrelated && note K && echo "Immaterial" >elif && diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index 04c0509c47..ef0cbceafe 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -176,7 +176,8 @@ test_expect_success 'up-to-date merge without common ancestor' ' test_tick && ( cd repo1 && - git pull ../repo2 master + git fetch ../repo2 master && + git merge --allow-unrelated-histories FETCH_HEAD ) ' diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 73fc240e85..3e692454a7 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -49,7 +49,7 @@ test_expect_success 'setup' ' test_expect_success 'initial merge' ' git remote add -f gui ../git-gui && - git merge -s ours --no-commit gui/master && + git merge -s ours --no-commit --allow-unrelated-histories gui/master && git read-tree --prefix=git-gui/ -u gui/master && git commit -m "Merge git-gui as our subdirectory" && git checkout -b work && diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh new file mode 100755 index 0000000000..20a3ffed69 --- /dev/null +++ b/t/t6044-merge-unrelated-index-changes.sh @@ -0,0 +1,153 @@ +#!/bin/sh + +test_description="merges with unrelated index changes" + +. ./test-lib.sh + +# Testcase for some simple merges +# A +# o-----o B +# \ +# \---o C +# \ +# \-o D +# \ +# o E +# Commit A: some file a +# Commit B: adds file b, modifies end of a +# Commit C: adds file c +# Commit D: adds file d, modifies beginning of a +# Commit E: renames a->subdir/a, adds subdir/e + +test_expect_success 'setup trivial merges' ' + seq 1 10 >a && + git add a && + test_tick && git commit -m A && + + git branch A && + git branch B && + git branch C && + git branch D && + git branch E && + + git checkout B && + echo b >b && + echo 11 >>a && + git add a b && + test_tick && git commit -m B && + + git checkout C && + echo c >c && + git add c && + test_tick && git commit -m C && + + git checkout D && + seq 2 10 >a && + echo d >d && + git add a d && + test_tick && git commit -m D && + + git checkout E && + mkdir subdir && + git mv a subdir/a && + echo e >subdir/e && + git add subdir && + test_tick && git commit -m E +' + +test_expect_success 'ff update' ' + git reset --hard && + git checkout A^0 && + + touch random_file && git add random_file && + + git merge E^0 && + + test_must_fail git rev-parse HEAD:random_file && + test "$(git diff --name-only --cached E)" = "random_file" +' + +test_expect_success 'ff update, important file modified' ' + git reset --hard && + git checkout A^0 && + + mkdir subdir && + touch subdir/e && + git add subdir/e && + + test_must_fail git merge E^0 +' + +test_expect_success 'resolve, trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve C^0 +' + +test_expect_success 'resolve, non-trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve D^0 +' + +test_expect_success 'recursive' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive C^0 +' + +test_expect_success 'octopus, unrelated file touched' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'octopus, related file removed' ' + git reset --hard && + git checkout B^0 && + + git rm b && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'octopus, related file modified' ' + git reset --hard && + git checkout B^0 && + + echo 12 >>a && git add a && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'ours' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s ours C^0 +' + +test_expect_success 'subtree' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s subtree E^0 +' + +test_done diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 10b1452766..1c6952d049 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -19,7 +19,7 @@ test_expect_success 'setup' ' git checkout --orphan tmp && test_commit start2 && git checkout master && - git merge -m next start2 && + git merge -m next --allow-unrelated-histories start2 && test_commit final && test_seq 40 | diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index bcf472bf51..70afb44271 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -5,11 +5,14 @@ test_description='test for-each-refs usage of ref-filter APIs' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh -if ! test_have_prereq GPG -then - skip_all="skipping for-each-ref tests, GPG not available" - test_done -fi +test_prepare_expect () { + if test_have_prereq GPG + then + cat + else + sed '/signed/d' + fi +} test_expect_success 'setup some history and refs' ' test_commit one && @@ -17,8 +20,13 @@ test_expect_success 'setup some history and refs' ' test_commit three && git checkout -b side && test_commit four && - git tag -s -m "A signed tag message" signed-tag && - git tag -s -m "Annonated doubly" double-tag signed-tag && + git tag -m "An annotated tag" annotated-tag && + git tag -m "Annonated doubly" doubly-annotated-tag annotated-tag && + if test_have_prereq GPG + then + git tag -s -m "A signed tag" signed-tag && + git tag -s -m "Signed doubly" doubly-signed-tag signed-tag + fi && git checkout master && git update-ref refs/odd/spot master ' @@ -34,8 +42,9 @@ test_expect_success 'filtering with --points-at' ' ' test_expect_success 'check signed tags with --points-at' ' - sed -e "s/Z$//" >expect <<-\EOF && + test_prepare_expect <<-\EOF | sed -e "s/Z$//" >expect && refs/heads/side Z + refs/tags/annotated-tag four refs/tags/four Z refs/tags/signed-tag four EOF @@ -56,9 +65,11 @@ test_expect_success 'filtering with --merged' ' ' test_expect_success 'filtering with --no-merged' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refs/heads/side - refs/tags/double-tag + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag refs/tags/four refs/tags/signed-tag EOF @@ -67,11 +78,13 @@ test_expect_success 'filtering with --no-merged' ' ' test_expect_success 'filtering with --contains' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refs/heads/master refs/heads/side refs/odd/spot - refs/tags/double-tag + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag refs/tags/four refs/tags/signed-tag refs/tags/three @@ -86,11 +99,13 @@ test_expect_success '%(color) must fail' ' ' test_expect_success 'left alignment is default' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refname is refs/heads/master |refs/heads/master refname is refs/heads/side |refs/heads/side refname is refs/odd/spot |refs/odd/spot - refname is refs/tags/double-tag|refs/tags/double-tag + refname is refs/tags/annotated-tag|refs/tags/annotated-tag + refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag refname is refs/tags/four |refs/tags/four refname is refs/tags/one |refs/tags/one refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -102,11 +117,13 @@ test_expect_success 'left alignment is default' ' ' test_expect_success 'middle alignment' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot - |refname is refs/tags/double-tag|refs/tags/double-tag + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag | refname is refs/tags/four |refs/tags/four | refname is refs/tags/one |refs/tags/one |refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -118,11 +135,13 @@ test_expect_success 'middle alignment' ' ' test_expect_success 'right alignment' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && | refname is refs/heads/master|refs/heads/master | refname is refs/heads/side|refs/heads/side | refname is refs/odd/spot|refs/odd/spot - |refname is refs/tags/double-tag|refs/tags/double-tag + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag | refname is refs/tags/four|refs/tags/four | refname is refs/tags/one|refs/tags/one |refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -133,11 +152,13 @@ test_expect_success 'right alignment' ' test_cmp expect actual ' -cat >expect <<-\EOF +test_prepare_expect >expect <<-\EOF | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot -| refname is refs/tags/double-tag |refs/tags/double-tag +| refname is refs/tags/annotated-tag |refs/tags/annotated-tag +|refname is refs/tags/doubly-annotated-tag |refs/tags/doubly-annotated-tag +| refname is refs/tags/doubly-signed-tag |refs/tags/doubly-signed-tag | refname is refs/tags/four |refs/tags/four | refname is refs/tags/one |refs/tags/one | refname is refs/tags/signed-tag |refs/tags/signed-tag @@ -178,11 +199,13 @@ EOF # Individual atoms inside %(align:...) and %(end) must not be quoted. test_expect_success 'alignment with format quote' " - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && |' '\''master| A U Thor'\'' '| |' '\''side| A U Thor'\'' '| |' '\''odd/spot| A U Thor'\'' '| - |' '\''double-tag| '\'' '| + |' '\''annotated-tag| '\'' '| + |' '\''doubly-annotated-tag| '\'' '| + |' '\''doubly-signed-tag| '\'' '| |' '\''four| A U Thor'\'' '| |' '\''one| A U Thor'\'' '| |' '\''signed-tag| '\'' '| @@ -194,11 +217,13 @@ test_expect_success 'alignment with format quote' " " test_expect_success 'nested alignment with quote formatting' " - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && |' master '| |' side '| |' odd/spot '| - |' double-tag '| + |' annotated-tag '| + |'doubly-annotated-tag '| + |'doubly-signed-tag '| |' four '| |' one '| |' signed-tag '| @@ -210,14 +235,16 @@ test_expect_success 'nested alignment with quote formatting' " " test_expect_success 'check `%(contents:lines=1)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master |three side |four odd/spot |three - double-tag |Annonated doubly + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly four |four one |one - signed-tag |A signed tag message + signed-tag |A signed tag three |three two |two EOF @@ -226,11 +253,13 @@ test_expect_success 'check `%(contents:lines=1)`' ' ' test_expect_success 'check `%(contents:lines=0)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master | side | odd/spot | - double-tag | + annotated-tag | + doubly-annotated-tag | + doubly-signed-tag | four | one | signed-tag | @@ -242,14 +271,16 @@ test_expect_success 'check `%(contents:lines=0)`' ' ' test_expect_success 'check `%(contents:lines=99999)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master |three side |four odd/spot |three - double-tag |Annonated doubly + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly four |four one |one - signed-tag |A signed tag message + signed-tag |A signed tag three |three two |two EOF diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 4008faead8..4a2570ed95 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -292,6 +292,9 @@ test_expect_success 'setup submodule' ' echo content >file && git add file && git commit -m "added sub and file" && + mkdir -p deep/directory/hierachy && + git submodule add ./. deep/directory/hierachy/sub && + git commit -m "added another submodule" && git branch submodule ' @@ -475,4 +478,17 @@ test_expect_success 'mv -k does not accidentally destroy submodules' ' git checkout . ' +test_expect_success 'moving a submodule in nested directories' ' + ( + cd deep && + git mv directory ../ && + # git status would fail if the update of linking git dir to + # work dir of the submodule failed. + git status && + git config -f ../.gitmodules submodule.deep/directory/hierachy/sub.path >../actual && + echo "directory/hierachy/sub" >../expect + ) && + test_cmp actual expect +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index cf3469b142..f9b7d79af5 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -775,6 +775,47 @@ test_expect_success GPG '-s implies annotated tag' ' test_cmp expect actual ' +get_tag_header forcesignannotated-implied-sign $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag -s implied if configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -m "A message" forcesignannotated-implied-sign && + get_tag_msg forcesignannotated-implied-sign >actual && + test_cmp expect actual +' + +test_expect_success GPG \ + 'lightweight with no message when configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag forcesignannotated-lightweight && + tag_exists forcesignannotated-lightweight && + test_must_fail git tag -v forcesignannotated-no-message +' + +get_tag_header forcesignannotated-annotate $commit commit $time >expect +echo "A message" >>expect +test_expect_success GPG \ + 'git tag -a disable configured tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -a -m "A message" forcesignannotated-annotate && + get_tag_msg forcesignannotated-annotate >actual && + test_cmp expect actual && + test_must_fail git tag -v forcesignannotated-annotate +' + +get_tag_header forcesignannotated-disabled $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag --sign enable GPG sign' \ + 'test_config tag.forcesignannotated false && + git tag --sign -m "A message" forcesignannotated-disabled && + get_tag_msg forcesignannotated-disabled >actual && + test_cmp expect actual +' + test_expect_success GPG \ 'trying to create a signed tag with non-existing -F file should fail' ' ! test -f nonexistingfile && diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 4608e71343..07079a41c4 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -112,4 +112,17 @@ test_expect_success GPG 'verify signatures with --raw' ' ) ' +test_expect_success GPG 'verify multiple tags' ' + tags="fourth-signed sixth-signed seventh-signed" && + for i in $tags + do + git verify-tag -v --raw $i || return 1 + done >expect.stdout 2>expect.stderr.1 && + grep "^.GNUPG:." <expect.stderr.1 >expect.stderr && + git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 && + grep "^.GNUPG:." <actual.stderr.1 >actual.stderr && + test_cmp expect.stdout actual.stdout && + test_cmp expect.stderr actual.stderr +' + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e1abd19230..f99f674ac7 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -462,7 +462,7 @@ test_expect_success 'update --init' ' git config --remove-section submodule.example && test_must_fail git config submodule.example.url && - git submodule update init > update.out && + git submodule update init 2> update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir init/.git && @@ -480,7 +480,7 @@ test_expect_success 'update --init from subdirectory' ' mkdir -p sub && ( cd sub && - git submodule update ../init >update.out && + git submodule update ../init 2>update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir ../init/.git && @@ -818,6 +818,47 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano ) ' +test_expect_success 'recursive relative submodules stay relative' ' + test_when_finished "rm -rf super clone2 subsub sub3" && + mkdir subsub && + ( + cd subsub && + git init && + >t && + git add t && + git commit -m "initial commit" + ) && + mkdir sub3 && + ( + cd sub3 && + git init && + >t && + git add t && + git commit -m "initial commit" && + git submodule add ../subsub dirdir/subsub && + git commit -m "add submodule subsub" + ) && + mkdir super && + ( + cd super && + git init && + >t && + git add t && + git commit -m "initial commit" && + git submodule add ../sub3 && + git commit -m "add submodule sub" + ) && + git clone super clone2 && + ( + cd clone2 && + git submodule update --init --recursive && + echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect && + echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect + ) && + test_cmp clone2/sub3/.git_expect clone2/sub3/.git && + test_cmp clone2/sub3/dirdir/subsub/.git_expect clone2/sub3/dirdir/subsub/.git +' + test_expect_success 'submodule add with an existing name fails unless forced' ' ( cd addtest2 && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 68ea31d693..fd741f506f 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -63,6 +63,10 @@ test_expect_success 'setup a submodule tree' ' git submodule add ../none none && test_tick && git commit -m "none" + ) && + git clone . recursivesuper && + ( cd recursivesuper + git submodule add ../super super ) ' @@ -95,6 +99,35 @@ test_expect_success 'submodule update from subdirectory' ' ) ' +supersha1=$(git -C super rev-parse HEAD) +mergingsha1=$(git -C super/merging rev-parse HEAD) +nonesha1=$(git -C super/none rev-parse HEAD) +rebasingsha1=$(git -C super/rebasing rev-parse HEAD) +submodulesha1=$(git -C super/submodule rev-parse HEAD) +pwd=$(pwd) + +cat <<EOF >expect +Submodule path '../super': checked out '$supersha1' +Submodule 'merging' ($pwd/merging) registered for path '../super/merging' +Submodule 'none' ($pwd/none) registered for path '../super/none' +Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing' +Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule' +Submodule path '../super/merging': checked out '$mergingsha1' +Submodule path '../super/none': checked out '$nonesha1' +Submodule path '../super/rebasing': checked out '$rebasingsha1' +Submodule path '../super/submodule': checked out '$submodulesha1' +EOF + +test_expect_success 'submodule update --init --recursive from subdirectory' ' + git -C recursivesuper/super reset --hard HEAD^ && + (cd recursivesuper && + mkdir tmp && + cd tmp && + git submodule update --init --recursive ../super >../../actual + ) && + test_cmp expect actual +' + apos="'"; test_expect_success 'submodule update does not fetch already present commits' ' (cd submodule && @@ -311,16 +344,59 @@ test_expect_success 'submodule update - command in .git/config' ' ) ' +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path 'submodule' +EOF + test_expect_success 'submodule update - command in .git/config catches failure' ' (cd super && git config submodule.submodule.update "!false" ) && (cd super/submodule && - git reset --hard HEAD^ + git reset --hard $submodulesha1^ ) && (cd super && - test_must_fail git submodule update submodule - ) + test_must_fail git submodule update submodule 2>../actual + ) && + test_cmp actual expect +' + +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path '../submodule' +EOF + +test_expect_success 'submodule update - command in .git/config catches failure -- subdirectory' ' + (cd super && + git config submodule.submodule.update "!false" + ) && + (cd super/submodule && + git reset --hard $submodulesha1^ + ) && + (cd super && + mkdir tmp && cd tmp && + test_must_fail git submodule update ../submodule 2>../../actual + ) && + test_cmp actual expect +' + +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path '../super/submodule' +Failed to recurse into submodule path '../super' +EOF + +test_expect_success 'recursive submodule update - command in .git/config catches failure -- subdirectory' ' + (cd recursivesuper && + git submodule update --remote super && + git add super && + git commit -m "update to latest to have more than one commit in submodules" + ) && + git -C recursivesuper/super config submodule.submodule.update "!false" && + git -C recursivesuper/super/submodule reset --hard $submodulesha1^ && + (cd recursivesuper && + mkdir -p tmp && cd tmp && + test_must_fail git submodule update --recursive ../super 2>../../actual + ) && + test_cmp actual expect ' test_expect_success 'submodule init does not copy command into .git/config' ' @@ -774,4 +850,31 @@ test_expect_success 'submodule update --recursive drops module name before recur test_i18ngrep "Submodule path .deeper/submodule/subsubmodule.: checked out" actual ) ' + +test_expect_success 'submodule update can be run in parallel' ' + (cd super2 && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git submodule update && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 9 && + grep "9 tasks" trace.out + ) +' + +test_expect_success 'git clone passes the parallel jobs config on to submodules' ' + test_when_finished "rm -rf super4" && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 7 . super4 && + grep "7 tasks" trace.out && + rm -rf super4 && + git config --global submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules . super4 && + grep "8 tasks" trace.out && + rm -rf super4 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 9 . super4 && + grep "9 tasks" trace.out && + rm -rf super4 +' + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 7ca10b8606..6ba5daf42e 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -178,6 +178,26 @@ test_expect_success 'test messages from "foreach --recursive"' ' ' cat > expect <<EOF +Entering '../nested1' +Entering '../nested1/nested2' +Entering '../nested1/nested2/nested3' +Entering '../nested1/nested2/nested3/submodule' +Entering '../sub1' +Entering '../sub2' +Entering '../sub3' +EOF + +test_expect_success 'test messages from "foreach --recursive" from subdirectory' ' + ( + cd clone2 && + mkdir untracked && + cd untracked && + git submodule foreach --recursive >../../actual + ) && + test_i18ncmp expect actual +' + +cat > expect <<EOF nested1-nested1 nested2-nested2 nested3-nested3 @@ -242,8 +262,12 @@ test_expect_success 'test "status --recursive"' ' test_cmp expect actual ' -sed -e "/nested2 /s/.*/+$nested2sha1 nested1\/nested2 (file2~1)/;/sub[1-3]/d" < expect > expect2 -mv -f expect2 expect +cat > expect <<EOF + $nested1sha1 nested1 (heads/master) ++$nested2sha1 nested1/nested2 (file2~1) + $nested3sha1 nested1/nested2/nested3 (heads/master) + $submodulesha1 nested1/nested2/nested3/submodule (heads/master) +EOF test_expect_success 'ensure "status --cached --recursive" preserves the --cached flag' ' ( @@ -257,6 +281,27 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached test_cmp expect actual ' +nested2sha1=$(git -C clone3/nested1/nested2 rev-parse HEAD) + +cat > expect <<EOF + $nested1sha1 ../nested1 (heads/master) ++$nested2sha1 ../nested1/nested2 (file2) + $nested3sha1 ../nested1/nested2/nested3 (heads/master) + $submodulesha1 ../nested1/nested2/nested3/submodule (heads/master) + $sub1sha1 ../sub1 ($sub1sha1_short) + $sub2sha1 ../sub2 ($sub2sha1_short) + $sub3sha1 ../sub3 (heads/master) +EOF + +test_expect_success 'test "status --recursive" from sub directory' ' + ( + cd clone3 && + mkdir tmp && cd tmp && + git submodule status --recursive > ../../actual + ) && + test_cmp expect actual +' + test_expect_success 'use "git clone --recursive" to checkout all submodules' ' git clone --recursive super clone4 && ( diff --git a/t/t7412-submodule--helper.sh b/t/t7412-submodule--helper.sh new file mode 100755 index 0000000000..149d42864f --- /dev/null +++ b/t/t7412-submodule--helper.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (c) 2016 Jacob Keller +# + +test_description='Basic plumbing support of submodule--helper + +This test verifies the submodule--helper plumbing command used to implement +git-submodule. +' + +. ./test-lib.sh + +test_expect_success 'sanitize-config clears configuration' ' + git -c user.name="Some User" submodule--helper sanitize-config >actual && + test_must_be_empty actual +' + +sq="'" +test_expect_success 'sanitize-config keeps credential.helper' ' + git -c credential.helper=helper submodule--helper sanitize-config >actual && + echo "${sq}credential.helper=helper${sq}" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 63e04277f9..900f7de05a 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -200,6 +200,26 @@ test_expect_success '--amend --edit of empty message' ' test_cmp expect msg ' +test_expect_success '--amend to set message to empty' ' + echo bata >file && + git add file && + git commit -m "unamended" && + git commit --amend --allow-empty-message -m "" && + git diff-tree -s --format=%s HEAD >msg && + echo "" >expect && + test_cmp expect msg +' + +test_expect_success '--amend to set empty message needs --allow-empty-message' ' + echo conga >file && + git add file && + git commit -m "unamended" && + test_must_fail git commit --amend -m "" && + git diff-tree -s --format=%s HEAD >msg && + echo "unamended" >expect && + test_cmp expect msg +' + test_expect_success '-m --edit' ' echo amended >expect && git commit --allow-empty -m buffer && diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh index 0cb9d11f21..5d56c38546 100755 --- a/t/t7605-merge-resolve.sh +++ b/t/t7605-merge-resolve.sh @@ -27,7 +27,7 @@ test_expect_success 'setup' ' git tag c3 ' -test_expect_success 'merge c1 to c2' ' +merge_c1_to_c2_cmds=' git reset --hard c1 && git merge -s resolve c2 && test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && @@ -41,6 +41,10 @@ test_expect_success 'merge c1 to c2' ' test 3 = $(git ls-files | wc -l) ' +test_expect_success 'merge c1 to c2' "$merge_c1_to_c2_cmds" + +test_expect_success 'merge c1 to c2, again' "$merge_c1_to_c2_cmds" + test_expect_success 'merge c2 to c3 (fails)' ' git reset --hard c2 && test_must_fail git merge -s resolve c3 diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 4e713f7aa5..ff7a9e968f 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -20,7 +20,7 @@ difftool_test_setup () prompt_given () { prompt="$1" - test "$prompt" = "Launch 'test-tool' [Y/n]: branch" + test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } # Create a file on master and change it on branch diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index d708cbf032..432c61d246 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -45,7 +45,8 @@ test_expect_success 'setup' ' touch secondrootfile && git add secondrootfile && git commit -m "second root") && - git pull secondroot master && + git fetch secondroot master && + git merge --allow-unrelated-histories FETCH_HEAD && git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" && diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index 66d3fc91a7..eb9a8ed197 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -223,12 +223,12 @@ build_gendouble() { import sys import struct - s = struct.pack(">LL18s", + s = struct.pack(b">LL18s", 0x00051607, # AppleDouble 0x00020000, # version 2 - "" # pad to 26 bytes + b"" # pad to 26 bytes ) - sys.stdout.write(s) + getattr(sys.stdout, 'buffer', sys.stdout).write(s) EOF } diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index 0b664a377c..110a7e7924 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -13,6 +13,10 @@ test_file_in_lfs () { FILE="$1" && SIZE="$2" && EXPECTED_CONTENT="$3" && + sed -n '1,1 p' "$FILE" | grep "^version " && + sed -n '2,2 p' "$FILE" | grep "^oid " && + sed -n '3,3 p' "$FILE" | grep "^size " && + test_line_count = 3 "$FILE" && cat "$FILE" | grep "size $SIZE" && HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") && LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" && @@ -265,7 +269,7 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr # We only import HEAD here ("@all" is missing!) git p4 clone --destination="$git" //depot && - test_file_in_lfs file6.bin 13 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" + test_file_in_lfs file6.bin 39 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" && test_file_count_in_dir ".git/lfs/objects" 1 && cat >expect <<-\EOF && diff --git a/t/t9826-git-p4-keep-empty-commits.sh b/t/t9826-git-p4-keep-empty-commits.sh index be12960d39..fa8b9daf1f 100755 --- a/t/t9826-git-p4-keep-empty-commits.sh +++ b/t/t9826-git-p4-keep-empty-commits.sh @@ -47,23 +47,23 @@ test_expect_success 'Clone repo root path with all history' ' git init . && git p4 clone --use-client-spec --destination="$git" //depot@all && cat >expect <<-\EOF && -Remove file 4 -[git-p4: depot-paths = "//depot/": change = 6] + Remove file 4 + [git-p4: depot-paths = "//depot/": change = 6] -Remove file 3 -[git-p4: depot-paths = "//depot/": change = 5] + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] -Add file 4 -[git-p4: depot-paths = "//depot/": change = 4] + Add file 4 + [git-p4: depot-paths = "//depot/": change = 4] -Add file 3 -[git-p4: depot-paths = "//depot/": change = 3] + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] -Add file 2 -[git-p4: depot-paths = "//depot/": change = 2] + Add file 2 + [git-p4: depot-paths = "//depot/": change = 2] -Add file 1 -[git-p4: depot-paths = "//depot/": change = 1] + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] EOF git log --format=%B >actual && @@ -80,23 +80,23 @@ test_expect_success 'Clone repo subdir with all history but keep empty commits' git config git-p4.keepEmptyCommits true && git p4 clone --use-client-spec --destination="$git" //depot@all && cat >expect <<-\EOF && -Remove file 4 -[git-p4: depot-paths = "//depot/": change = 6] + Remove file 4 + [git-p4: depot-paths = "//depot/": change = 6] -Remove file 3 -[git-p4: depot-paths = "//depot/": change = 5] + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] -Add file 4 -[git-p4: depot-paths = "//depot/": change = 4] + Add file 4 + [git-p4: depot-paths = "//depot/": change = 4] -Add file 3 -[git-p4: depot-paths = "//depot/": change = 3] + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] -Add file 2 -[git-p4: depot-paths = "//depot/": change = 2] + Add file 2 + [git-p4: depot-paths = "//depot/": change = 2] -Add file 1 -[git-p4: depot-paths = "//depot/": change = 1] + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] EOF git log --format=%B >actual && @@ -112,14 +112,14 @@ test_expect_success 'Clone repo subdir with all history' ' git init . && git p4 clone --use-client-spec --destination="$git" --verbose //depot@all && cat >expect <<-\EOF && -Remove file 3 -[git-p4: depot-paths = "//depot/": change = 5] + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] -Add file 3 -[git-p4: depot-paths = "//depot/": change = 3] + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] -Add file 1 -[git-p4: depot-paths = "//depot/": change = 1] + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] EOF git log --format=%B >actual && diff --git a/t/t9828-git-p4-map-user.sh b/t/t9828-git-p4-map-user.sh new file mode 100755 index 0000000000..e20395c89f --- /dev/null +++ b/t/t9828-git-p4-map-user.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='Clone repositories and map users' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo with different users' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + >author.txt && + p4 add author.txt && + p4 submit -d "Add file author\\n" && + + P4USER=mmax && + >max.txt && + p4 add max.txt && + p4 submit -d "Add file max" && + + P4USER=eri && + >moritz.txt && + p4 add moritz.txt && + p4 submit -d "Add file moritz" && + + P4USER=no && + >nobody.txt && + p4 add nobody.txt && + p4 submit -d "Add file nobody" + ) +' + +test_expect_success 'Clone repo root path with all history' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config --add git-p4.mapUser "mmax = Max Musterman <max@example.com> " && + git config --add git-p4.mapUser " eri=Erika Musterman <erika@example.com>" && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + no <no@client> + Erika Musterman <erika@example.com> + Max Musterman <max@example.com> + Dr. author <author@example.com> + EOF + git log --format="%an <%ae>" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9829-git-p4-jobs.sh b/t/t9829-git-p4-jobs.sh new file mode 100755 index 0000000000..971aeeea1f --- /dev/null +++ b/t/t9829-git-p4-jobs.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +test_description='git p4 retrieve job info' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'add p4 jobs' ' + ( + p4_add_job TESTJOB-A && + p4_add_job TESTJOB-B + ) +' + +test_expect_success 'add p4 files' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + >file1 && + p4 add file1 && + p4 submit -d "Add file 1" + ) +' + +test_expect_success 'check log message of changelist with no jobs' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'add TESTJOB-A to change 1' ' + ( + cd "$cli" && + p4 fix -c 1 TESTJOB-A + ) +' + +test_expect_success 'check log message of changelist with one job' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + Jobs: TESTJOB-A + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'add TESTJOB-B to change 1' ' + ( + cd "$cli" && + p4 fix -c 1 TESTJOB-B + ) +' + +test_expect_success 'check log message of changelist with more jobs' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + Jobs: TESTJOB-A TESTJOB-B + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index ffbfa0efb8..0db4469c89 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -107,7 +107,7 @@ test_expect_success 'prompt - describe detached head - contains' ' ' test_expect_success 'prompt - describe detached head - branch' ' - printf " ((b1~1))" >expected && + printf " ((tags/t2~1))" >expected && git checkout b1^ && test_when_finished "git checkout master" && ( diff --git a/t/test-lib.sh b/t/test-lib.sh index 0b47eb6bb2..286c5f33d1 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -202,13 +202,13 @@ do } run_list=$1; shift ;; --run=*) - run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;; + run_list=${1#--*=}; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; --verbose-only=*) - verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + verbose_only=${1#--*=} shift ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests @@ -222,15 +222,15 @@ do valgrind=memcheck shift ;; --valgrind=*) - valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)') + valgrind=${1#--*=} shift ;; --valgrind-only=*) - valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + valgrind_only=${1#--*=} shift ;; --tee) shift ;; # was handled already --root=*) - root=$(expr "z$1" : 'z[^=]*=\(.*\)') + root=${1#--*=} shift ;; --chain-lint) GIT_TEST_CHAIN_LINT=1 @@ -854,10 +854,10 @@ test -d "$GIT_BUILD_DIR"/templates/blt || { error "You haven't built things yet, have you?" } -if ! test -x "$GIT_BUILD_DIR"/test-chmtime +if ! test -x "$GIT_BUILD_DIR"/t/helper/test-chmtime then echo >&2 'You need to build test-chmtime:' - echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' + echo >&2 'Run "make t/helper/test-chmtime" in the source (toplevel) directory' exit 1 fi @@ -6,6 +6,59 @@ const char *tag_type = "tag"; +static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) +{ + struct signature_check sigc; + size_t payload_size; + int ret; + + memset(&sigc, 0, sizeof(sigc)); + + payload_size = parse_signature(buf, size); + + if (size == payload_size) { + if (flags & GPG_VERIFY_VERBOSE) + write_in_full(1, buf, payload_size); + return error("no signature found"); + } + + ret = check_signature(buf, payload_size, buf + payload_size, + size - payload_size, &sigc); + print_signature_buffer(&sigc, flags); + + signature_check_clear(&sigc); + return ret; +} + +int gpg_verify_tag(const unsigned char *sha1, const char *name_to_report, + unsigned flags) +{ + enum object_type type; + char *buf; + unsigned long size; + int ret; + + type = sha1_object_info(sha1, NULL); + if (type != OBJ_TAG) + return error("%s: cannot verify a non-tag object of type %s.", + name_to_report ? + name_to_report : + find_unique_abbrev(sha1, DEFAULT_ABBREV), + typename(type)); + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("%s: unable to read file.", + name_to_report ? + name_to_report : + find_unique_abbrev(sha1, DEFAULT_ABBREV)); + + ret = run_gpg_verify(buf, size, flags); + + free(buf); + return ret; +} + struct object *deref_tag(struct object *o, const char *warn, int warnlen) { while (o && o->type == OBJ_TAG) @@ -17,5 +17,7 @@ extern int parse_tag_buffer(struct tag *item, const void *data, unsigned long si extern int parse_tag(struct tag *item); extern struct object *deref_tag(struct object *, const char *, int); extern struct object *deref_tag_noverify(struct object *); +extern int gpg_verify_tag(const unsigned char *sha1, + const char *name_to_report, unsigned flags); #endif /* TAG_H */ diff --git a/tree-diff.c b/tree-diff.c index 4dda9a14ab..ff4e0d3cb6 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -183,7 +183,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, if (t) { /* path present in resulting tree */ - sha1 = tree_entry_extract(t, &path, &mode); + sha1 = tree_entry_extract(t, &path, &mode)->hash; pathlen = tree_entry_len(&t->entry); isdir = S_ISDIR(mode); } else { @@ -229,7 +229,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, DIFF_STATUS_ADDED; if (tpi_valid) { - sha1_i = tp[i].entry.sha1; + sha1_i = tp[i].entry.oid->hash; mode_i = tp[i].entry.mode; } else { @@ -270,7 +270,7 @@ static struct combine_diff_path *emit_path(struct combine_diff_path *p, /* same rule as in emitthis */ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); - parents_sha1[i] = tpi_valid ? tp[i].entry.sha1 + parents_sha1[i] = tpi_valid ? tp[i].entry.oid->hash : NULL; } @@ -482,7 +482,7 @@ static struct combine_diff_path *ll_diff_tree_paths( continue; /* diff(t,pi) != ΓΈ */ - if (hashcmp(t.entry.sha1, tp[i].entry.sha1) || + if (oidcmp(t.entry.oid, tp[i].entry.oid) || (t.entry.mode != tp[i].entry.mode)) continue; diff --git a/tree-walk.c b/tree-walk.c index cd4bb2c38b..ce27842439 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -38,7 +38,7 @@ static void decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned /* Initialize the descriptor entry */ desc->entry.path = path; desc->entry.mode = canon_mode(mode); - desc->entry.sha1 = (const unsigned char *)(path + len); + desc->entry.oid = (const struct object_id *)(path + len); } void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size) @@ -76,7 +76,7 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a) void update_tree_entry(struct tree_desc *desc) { const void *buf = desc->buffer; - const unsigned char *end = desc->entry.sha1 + 20; + const unsigned char *end = desc->entry.oid->hash + 20; unsigned long size = desc->size; unsigned long len = end - (const unsigned char *)buf; @@ -110,7 +110,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base) pathlen--; info->pathlen = pathlen ? pathlen + 1 : 0; info->name.path = base; - info->name.sha1 = (void *)(base + pathlen + 1); + info->name.oid = (void *)(base + pathlen + 1); if (pathlen) info->prev = &dummy; } @@ -433,10 +433,10 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char int namelen = strlen(name); while (t->size) { const char *entry; - const unsigned char *sha1; + const struct object_id *oid; int entrylen, cmp; - sha1 = tree_entry_extract(t, &entry, mode); + oid = tree_entry_extract(t, &entry, mode); entrylen = tree_entry_len(&t->entry); update_tree_entry(t); if (entrylen > namelen) @@ -447,7 +447,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (cmp < 0) break; if (entrylen == namelen) { - hashcpy(result, sha1); + hashcpy(result, oid->hash); return 0; } if (name[entrylen] != '/') @@ -455,10 +455,10 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { - hashcpy(result, sha1); + hashcpy(result, oid->hash); return 0; } - return get_tree_entry(sha1, name + entrylen, result, mode); + return get_tree_entry(oid->hash, name + entrylen, result, mode); } return -1; } diff --git a/tree-walk.h b/tree-walk.h index 174eb617df..97a7d6957e 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -2,7 +2,7 @@ #define TREE_WALK_H struct name_entry { - const unsigned char *sha1; + const struct object_id *oid; const char *path; unsigned int mode; }; @@ -13,16 +13,16 @@ struct tree_desc { unsigned int size; }; -static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep) +static inline const struct object_id *tree_entry_extract(struct tree_desc *desc, const char **pathp, unsigned int *modep) { *pathp = desc->entry.path; *modep = desc->entry.mode; - return desc->entry.sha1; + return desc->entry.oid; } static inline int tree_entry_len(const struct name_entry *ne) { - return (const char *)ne->sha1 - ne->path - 1; + return (const char *)ne->oid - ne->path - 1; } void update_tree_entry(struct tree_desc *); @@ -76,7 +76,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, continue; } - switch (fn(entry.sha1, base, + switch (fn(entry.oid->hash, base, entry.path, entry.mode, stage, context)) { case 0: continue; @@ -87,19 +87,19 @@ static int read_tree_1(struct tree *tree, struct strbuf *base, } if (S_ISDIR(entry.mode)) - hashcpy(sha1, entry.sha1); + hashcpy(sha1, entry.oid->hash); else if (S_ISGITLINK(entry.mode)) { struct commit *commit; - commit = lookup_commit(entry.sha1); + commit = lookup_commit(entry.oid->hash); if (!commit) die("Commit %s in submodule path %s%s not found", - sha1_to_hex(entry.sha1), + oid_to_hex(entry.oid), base->buf, entry.path); if (parse_commit(commit)) die("Invalid commit %s in submodule path %s%s", - sha1_to_hex(entry.sha1), + oid_to_hex(entry.oid), base->buf, entry.path); hashcpy(sha1, commit->tree->object.oid.hash); diff --git a/unpack-trees.c b/unpack-trees.c index 9f55cc28b9..11308e9e5b 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -475,7 +475,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, for (i = 0; i < n; i++, dirmask >>= 1) { const unsigned char *sha1 = NULL; if (dirmask & 1) - sha1 = names[i].sha1; + sha1 = names[i].oid->hash; buf[i] = fill_tree_descriptor(t+i, sha1); } @@ -591,7 +591,7 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con ce->ce_mode = create_ce_mode(n->mode); ce->ce_flags = create_ce_flags(stage); ce->ce_namelen = len; - hashcpy(ce->sha1, n->sha1); + hashcpy(ce->sha1, n->oid->hash); make_traverse_path(ce->name, info, n); return ce; @@ -43,12 +43,12 @@ static int process_tree(struct walker *walker, struct tree *tree) if (S_ISGITLINK(entry.mode)) continue; if (S_ISDIR(entry.mode)) { - struct tree *tree = lookup_tree(entry.sha1); + struct tree *tree = lookup_tree(entry.oid->hash); if (tree) obj = &tree->object; } else { - struct blob *blob = lookup_blob(entry.sha1); + struct blob *blob = lookup_blob(entry.oid->hash); if (blob) obj = &blob->object; } @@ -446,23 +446,6 @@ int git_mkstemp(char *path, size_t len, const char *template) return mkstemp(path); } -/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */ -int git_mkstemps(char *path, size_t len, const char *template, int suffix_len) -{ - const char *tmp; - size_t n; - - tmp = getenv("TMPDIR"); - if (!tmp) - tmp = "/tmp"; - n = snprintf(path, len, "%s/%s", tmp, template); - if (len <= n) { - errno = ENAMETOOLONG; - return -1; - } - return mkstemps(path, suffix_len); -} - /* Adapted from libiberty's mkstemp.c. */ #undef TMP_MAX diff --git a/wt-status.c b/wt-status.c index ef7486474a..1ea2ebe4c0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1063,9 +1063,7 @@ static void abbrev_sha1_in_line(struct strbuf *line) strbuf_addf(line, "%s", split[i]->buf); } } - for (i = 0; split[i]; i++) - strbuf_release(split[i]); - + strbuf_list_free(split); } static void read_rebase_todolist(const char *fname, struct string_list *lines) diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 4fb7e79410..7423f77fc8 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -41,6 +41,8 @@ extern "C" { #define XDF_IGNORE_BLANK_LINES (1 << 7) +#define XDF_COMPACTION_HEURISTIC (1 << 8) + #define XDL_EMIT_FUNCNAMES (1 << 0) #define XDL_EMIT_FUNCCONTEXT (1 << 2) diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 2358a2d632..b3c6848875 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -400,9 +400,23 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } +static int is_blank_line(xrecord_t **recs, long ix, long flags) +{ + return xdl_blankline(recs[ix]->ptr, recs[ix]->size, flags); +} + +static int recs_match(xrecord_t **recs, long ixs, long ix, long flags) +{ + return (recs[ixs]->ha == recs[ix]->ha && + xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, + recs[ix]->ptr, recs[ix]->size, + flags)); +} + int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; + unsigned int blank_lines; xrecord_t **recs = xdf->recs; /* @@ -436,14 +450,14 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { do { grpsiz = ix - ixs; + blank_lines = 0; /* * If the line before the current change group, is equal to * the last line of the current change group, shift backward * the group. */ - while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha && - xdl_recmatch(recs[ixs - 1]->ptr, recs[ixs - 1]->size, recs[ix - 1]->ptr, recs[ix - 1]->size, flags)) { + while (ixs > 0 && recs_match(recs, ixs - 1, ix - 1, flags)) { rchg[--ixs] = 1; rchg[--ix] = 0; @@ -470,8 +484,9 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { * the line next of the current change group, shift forward * the group. */ - while (ix < nrec && recs[ixs]->ha == recs[ix]->ha && - xdl_recmatch(recs[ixs]->ptr, recs[ixs]->size, recs[ix]->ptr, recs[ix]->size, flags)) { + while (ix < nrec && recs_match(recs, ixs, ix, flags)) { + blank_lines += is_blank_line(recs, ix, flags); + rchg[ixs++] = 0; rchg[ix++] = 1; @@ -498,6 +513,23 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { rchg[--ix] = 0; while (rchgo[--ixo]); } + + /* + * If a group can be moved back and forth, see if there is a + * blank line in the moving space. If there is a blank line, + * make sure the last blank line is the end of the group. + * + * As we already shifted the group forward as far as possible + * in the earlier loop, we need to shift it back only if at all. + */ + if ((flags & XDF_COMPACTION_HEURISTIC) && blank_lines) { + while (ixs > 0 && + !is_blank_line(recs, ix - 1, flags) && + recs_match(recs, ixs - 1, ix - 1, flags)) { + rchg[--ixs] = 1; + rchg[--ix] = 0; + } + } } return 0; |