Eric Blake <eblake@redhat.com> <ebb9@byu.net>
Eric Hanchrow <eric.hanchrow@gmail.com> <offby1@blarg.net>
Eric S. Raymond <esr@thyrsus.com>
+Eric Wong <e@80x24.org> <normalperson@yhbt.net>
Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com>
Eyvind Bernhardsen <eyvind.bernhardsen@gmail.com> <eyvind-git@orakel.ntnu.no>
Florian Achleitner <florian.achleitner.2.6.31@gmail.com> <florian.achleitner2.6.31@gmail.com>
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"
# t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X
- GIT_SKIP_TESTS="t9810 t9816"
+matrix:
+ include:
+ - env: Documentation
+ os: linux
+ compiler: clang
+ addons:
+ apt:
+ packages:
+ - asciidoc
+ - xmlto
+ before_install:
+ before_script:
+ script: ci/test-documentation.sh
+ after_failure:
+
before_install:
- >
case "${TRAVIS_OS_NAME:-linux}" in
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
;;
--- /dev/null
+Git v2.8.1 Release Notes
+========================
+
+Fixes since v2.8
+----------------
+
+ * "make rpmbuild" target was broken as its input, git.spec.in, was
+ not updated to match a file it describes that has been renamed
+ recently. This has been fixed.
--- /dev/null
+Git v2.8.2 Release Notes
+========================
+
+Fixes since v2.8.1
+------------------
+
+ * 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.
+
+ * Bunch of tests on "git clone" has been renumbered for better
+ organization.
+
+ * The tests that involve running httpd leaked the system-wide
+ configuration in /etc/gitconfig to the tested environment.
+
+ * "index-pack --keep=<msg>" was broken since v2.1.0 timeframe.
+
+ * "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.
+
+ * 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.
+
+ * 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.
+
+ * "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).
+
+ * Build updates for MSVC.
+
+ * "git diff -M" used to work better when two originally identical
+ files A and B got renamed to X/A and X/B by pairing A to X/A and B
+ to X/B, but this was broken in the 2.0 timeframe.
+
+ * "git send-pack --all <there>" was broken when its command line
+ option parsing was written in the 2.6 timeframe.
+
+ * When running "git blame $path" with unnormalized data in the index
+ for the path, the data in the working tree was blamed, even though
+ "git add" would not have changed what is already in the index, due
+ to "safe crlf" that disables the line-end conversion. It has been
+ corrected.
+
+Also contains minor documentation updates and code clean-ups.
--- /dev/null
+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.
+
+ * The test scripts for "git p4" (but not "git p4" implementation
+ itself) has been updated so that they would work even on a system
+ where the installed version of Python is python 3.
+
+ * The "user.useConfigOnly" configuration variable makes it an error
+ if users do not explicitly set user.name and user.email. However,
+ its check was not done early enough and allowed another error to
+ trigger, reporting that the default value we guessed from the
+ system setting was unusable. This was a suboptimal end-user
+ experience as we want the users to set user.name/user.email without
+ relying on the auto-detection at all.
+
+ * "git mv old new" did not adjust the path for a submodule that lives
+ as a subdirectory inside old/ directory correctly.
+
+ * "git push" from a corrupt repository that attempts to push a large
+ number of refs deadlocked; the thread to relay rejection notices
+ for these ref updates blocked on writing them to the main thread,
+ after the main thread at the receiving end notices that the push
+ failed and decides not to read these notices and return a failure.
+
+ * A question by "git send-email" to ask the identity of the sender
+ has been updated.
+
+ * Recent update to Git LFS broke "git p4" by changing the output from
+ its "lfs pointer" subcommand.
+
+ * Some multi-byte encoding can have a backslash byte as a later part
+ of one letter, which would confuse "highlight" filter used in
+ gitweb.
+
+Also contains minor documentation updates and code clean-ups.
--- /dev/null
+Git v2.8.4 Release Notes
+========================
+
+Fixes since v2.8.3
+------------------
+
+ * Documentation for "git merge --verify-signatures" has been updated
+ to clarify that the signature of only the commit at the tip is
+ verified. Also the phrasing used for signature and key validity is
+ adjusted to align with that used by OpenPGP.
+
+ * On Windows, .git and optionally any files whose name starts with a
+ dot are now marked as hidden, with a core.hideDotFiles knob to
+ customize this behaviour.
+
+ * Portability enhancement for "rebase -i" to help platforms whose
+ shell does not like "for i in <empty>" (which is not POSIX-kosher).
+
+ * "git fsck" learned to catch NUL byte in a commit object as
+ potential error and warn.
+
+ * CI test was taught to build documentation pages.
+
+ * Many 'linkgit:<git documentation page>' references were broken,
+ which are all fixed with this.
+
+Also contains other minor documentation updates and code clean-ups.
t/README for guidance.
When adding a new feature, make sure that you have new tests to show
-the feature triggers the new behaviour when it should, and to show the
-feature does not trigger when it shouldn't. Also make sure that the
-test suite passes after your commit. Do not forget to update the
-documentation to describe the updated behaviour.
-
-Speaking of the documentation, it is currently a liberal mixture of US
-and UK English norms for spelling and grammar, which is somewhat
-unfortunate. A huge patch that touches the files all over the place
-only to correct the inconsistency is not welcome, though. Potential
-clashes with other changes that can result from such a patch are not
-worth it. We prefer to gradually reconcile the inconsistencies in
-favor of US English, with small and easily digestible patches, as a
-side effect of doing some other real work in the vicinity (e.g.
-rewriting a paragraph for clarity, while turning en_UK spelling to
-en_US). Obvious typographical fixes are much more welcomed ("teh ->
-"the"), preferably submitted as independent patches separate from
-other documentation changes.
+the feature triggers the new behavior when it should, and to show the
+feature does not trigger when it shouldn't. After any code change, make
+sure that the entire test suite passes.
+
+If you have an account at GitHub (and you can get one for free to work
+on open source projects), you can use their Travis CI integration to
+test your changes on Linux, Mac (and hopefully soon Windows). See
+GitHub-Travis CI hints section for details.
+
+Do not forget to update the documentation to describe the updated
+behavior and make sure that the resulting documentation set formats
+well. It is currently a liberal mixture of US and UK English norms for
+spelling and grammar, which is somewhat unfortunate. A huge patch that
+touches the files all over the place only to correct the inconsistency
+is not welcome, though. Potential clashes with other changes that can
+result from such a patch are not worth it. We prefer to gradually
+reconcile the inconsistencies in favor of US English, with small and
+easily digestible patches, as a side effect of doing some other real
+work in the vicinity (e.g. rewriting a paragraph for clarity, while
+turning en_UK spelling to en_US). Obvious typographical fixes are much
+more welcomed ("teh -> "the"), preferably submitted as independent
+patches separate from other documentation changes.
Oh, another thing. We are picky about whitespaces. Make sure your
changes do not trigger errors with the sample pre-commit hook shipped
entitled "What's cooking in git.git" and "What's in git.git" giving
the status of various proposed changes.
+--------------------------------------------------
+GitHub-Travis CI hints
+
+With an account at GitHub (you can get one for free to work on open
+source projects), you can use Travis CI to test your changes on Linux,
+Mac (and hopefully soon Windows). You can find a successful example
+test build here: https://travis-ci.org/git/git/builds/120473209
+
+Follow these steps for the initial setup:
+
+ (1) Fork https://github.com/git/git to your GitHub account.
+ You can find detailed instructions how to fork here:
+ https://help.github.com/articles/fork-a-repo/
+
+ (2) Open the Travis CI website: https://travis-ci.org
+
+ (3) Press the "Sign in with GitHub" button.
+
+ (4) Grant Travis CI permissions to access your GitHub account.
+ You can find more information about the required permissions here:
+ https://docs.travis-ci.com/user/github-oauth-scopes
+
+ (5) Open your Travis CI profile page: https://travis-ci.org/profile
+
+ (6) Enable Travis CI builds for your Git fork.
+
+After the initial setup, Travis CI will run whenever you push new changes
+to your fork of Git on GitHub. You can monitor the test state of all your
+branches here: https://travis-ci.org/<Your GitHub handle>/git/branches
+
+If a branch did not pass all test cases then it is marked with a red
+cross. In that case you can click on the failing Travis CI job and
+scroll all the way down in the log. Find the line "<-- Click here to see
+detailed test output!" and click on the triangle next to the log line
+number to expand the detailed test output. Here is such a failing
+example: https://travis-ci.org/git/git/jobs/122676187
+
+Fix the problem and push your fix to your Git fork. This will trigger
+a new Travis CI build to ensure all tests pass.
+
+
------------------------------------------------
MUA specific hints
You can include one config file from another by setting the special
`include.path` variable to the name of the file to be included. The
+variable takes a pathname as its value, and is subject to tilde
+expansion.
+
+The
included file is expanded immediately, as if its contents had been
found at the location of the include directive. If the value of the
`include.path` variable is a relative path, the path is considered to be
relative to the configuration file in which the include directive was
-found. The value of `include.path` is subject to tilde expansion: `~/`
-is expanded to the value of `$HOME`, and `~user/` to the specified
-user's home directory. See below for examples.
+found. See below for examples.
+
Example
~~~~~~~
[include]
path = /path/to/foo.inc ; include by absolute path
path = foo ; expand "foo" relative to the current file
- path = ~/foo ; expand "foo" in your $HOME directory
+ path = ~/foo ; expand "foo" in your `$HOME` directory
Values
list of branch names in `log --decorate` output) is set to be
painted with `bold` or some other attribute.
+pathname::
+ A variable that takes a pathname value can be given a
+ string that begins with "`~/`" or "`~user/`", and the usual
+ tilde expansion happens to such a string: `~/`
+ is expanded to the value of `$HOME`, and `~user/` to the
+ specified user's home directory.
+
Variables
~~~~~~~~~
+
The default is true (when core.filemode is not specified in the config file).
+core.hideDotFiles::
+ (Windows-only) If true, mark newly-created directories and files whose
+ name starts with a dot as hidden. If 'dotGitOnly', only the `.git/`
+ directory is hidden, but no other files starting with a dot. The
+ default mode is 'dotGitOnly'.
+
core.ignoreCase::
If true, this option enables various workarounds to enable
Git to work better on filesystems that are not case sensitive,
core.logAllRefUpdates::
Enable the reflog. Updates to a ref <ref> is logged to the file
- "$GIT_DIR/logs/<ref>", by appending the new and old
+ "`$GIT_DIR/logs/<ref>`", by appending the new and old
SHA-1, the date/time and the reason of the update, but
only when the file exists. If this configuration
- variable is set to true, missing "$GIT_DIR/logs/<ref>"
+ variable is set to true, missing "`$GIT_DIR/logs/<ref>`"
file is automatically created for branch heads (i.e. under
refs/heads/), remote refs (i.e. under refs/remotes/),
note refs (i.e. under refs/notes/), and the symbolic ref HEAD.
Common unit suffixes of 'k', 'm', or 'g' are supported.
core.excludesFile::
- In addition to '.gitignore' (per-directory) and
- '.git/info/exclude', Git looks into this file for patterns
- of files which are not meant to be tracked. "`~/`" is expanded
- to the value of `$HOME` and "`~user/`" to the specified user's
- home directory. Its default value is $XDG_CONFIG_HOME/git/ignore.
- If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore
+ Specifies the pathname to the file that contains patterns to
+ describe paths that are not meant to be tracked, in addition
+ to '.gitignore' (per-directory) and '.git/info/exclude'.
+ Defaults to `$XDG_CONFIG_HOME/git/ignore`.
+ If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/ignore`
is used instead. See linkgit:gitignore[5].
core.askPass::
'.git/info/attributes', Git looks into this file for attributes
(see linkgit:gitattributes[5]). Path expansions are made the same
way as for `core.excludesFile`. Its default value is
- $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME is either not
- set or empty, $HOME/.config/git/attributes is used instead.
+ `$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not
+ set or empty, `$HOME/.config/git/attributes` is used instead.
core.editor::
Commands such as `commit` and `tag` that lets you edit
message. Defaults to true.
commit.template::
- Specify a file to use as the template for new commit messages.
- "`~/`" is expanded to the value of `$HOME` and "`~user/`" to the
- specified user's home directory.
+ Specify the pathname of a file to use as the template for
+ new commit messages.
credential.helper::
Specify an external helper to be called when a username or
'git worktree prune --expire 3.months.ago'.
This config variable can be used to set a different grace
period. The value "now" may be used to disable the grace
- period and prune $GIT_DIR/worktrees immediately, or "never"
+ period and prune `$GIT_DIR/worktrees` immediately, or "never"
may be used to suppress pruning.
gc.reflogExpire::
is executed outside of a git repository. Defaults to false.
gpg.program::
- Use this custom program instead of "gpg" found on $PATH when
+ Use this custom program instead of "`gpg`" found on `$PATH` when
making or verifying a PGP signature. The program must support the
same command-line interface as GPG, namely, to verify a detached
- signature, "gpg --verify $file - <$signature" is run, and the
+ signature, "`gpg --verify $file - <$signature`" is run, and the
program is expected to signal a good signature by exiting with
code 0, and to generate an ASCII-armored detached signature, the
- standard input of "gpg -bsau $key" is fed with the contents to be
+ standard input of "`gpg -bsau $key`" is fed with the contents to be
signed, and the program is expected to send the result to its
standard output.
made by the linkgit:git-gui[1]. The default is "5".
gui.displayUntracked::
- Determines if linkgit::git-gui[1] shows untracked files
+ Determines if linkgit:git-gui[1] shows untracked files
in the file list. The default is "true".
gui.encoding::
authentication.
http.cookieFile::
- File containing previously stored cookie lines which should be used
+ The pathname of a file containing previously stored cookie lines,
+ which should be used
in the Git http session, if they match the server. The file format
of the file to read cookies from should be plain HTTP headers or
- the Netscape/Mozilla cookie file format (see linkgit:curl[1]).
- NOTE that the file specified with http.cookieFile is only used as
+ the Netscape/Mozilla cookie file format (see `curl(1)`).
+ NOTE that the file specified with http.cookieFile is used only as
input unless http.saveCookies is set.
http.saveCookies::
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.
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
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>::
and, correspondingly, show differences character by character.
+
The regex can also be set via a diff driver or configuration option, see
-linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly
+linkgit:gitattributes[5] or linkgit:git-config[1]. Giving it explicitly
overrides any diff driver or configuration setting. Diff drivers
override configuration settings.
ifndef::git-format-patch[]
--check::
- Warn if changes introduce whitespace errors. What are
- considered whitespace errors is controlled by `core.whitespace`
+ Warn if changes introduce conflict markers or whitespace errors.
+ What are considered whitespace errors is controlled by `core.whitespace`
configuration. By default, trailing whitespaces (including
lines that solely consist of whitespaces) and a space character
that is immediately followed by a tab character inside the
Everyday Git With 20 Commands Or So
===================================
-This document has been moved to linkgit:giteveryday[1].
+This document has been moved to linkgit:giteveryday[7].
Please let the owners of the referring site know so that they can update the
link you clicked to get here.
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z]
[-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
- [--ignore-space-change | --ignore-whitespace ]
+ [--ignore-space-change | --ignore-whitespace]
[--whitespace=(nowarn|warn|fix|error|error-all)]
[--exclude=<path>] [--include=<path>] [--directory=<root>]
[--verbose] [--unsafe-paths] [<patch>...]
DESCRIPTION
-----------
Reads the supplied diff output (i.e. "a patch") and applies it to files.
+When running from a subdirectory in a repository, patched paths
+outside the directory are ignored.
With the `--index` option the patch is also applied to the index, and
with the `--cached` option the patch is only applied to the index.
Without these options, the command applies the patch only to files,
SEE ALSO
--------
linkgit:gitignore[5]
-linkgit:gitconfig[5]
+linkgit:git-config[1]
linkgit:git-ls-files[1]
GIT
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),
-. no section or name was provided (ret=2),
-. the section or key is invalid (ret=1),
-. 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).
+- The section or key is invalid (ret=1),
+- no section or name was provided (ret=2),
+- 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).
On success, the command returns the exit code 0.
found and the last value if multiple key values were found.
--get-all::
- Like get, but does not fail if the number of values for the key
- is not exactly one.
+ Like get, but returns all values for a multi-valued key.
--get-regexp::
Like --get-all, but interprets the name as a regular expression and
given URL is returned (if no such key exists, the value for
section.key is used as a fallback). When given just the
section as name, do so for all the keys in the section and
- list them.
+ list them. Returns error code 1 if no value is found.
--global::
For writing options: write to global `~/.gitconfig` file
The remote heads to update from. This is relative to
$GIT_DIR (e.g. "HEAD", "refs/heads/master"). When
unspecified, update from all heads the remote side has.
++
+If the remote has enabled the options `uploadpack.allowTipSHA1InWant` or
+`uploadpack.allowReachableSHA1InWant`, they may alternatively be 40-hex
+sha1s present on the remote.
SEE ALSO
--------
Remap to ancestor
~~~~~~~~~~~~~~~~~
-By using linkgit:rev-list[1] arguments, e.g., path limiters, you can limit the
+By using linkgit:git-rev-list[1] arguments, e.g., path limiters, you can limit the
set of revisions which get rewritten. However, positive refs on the command
line are distinguished: we don't let them be excluded by such limiters. For
this purpose, they are instead rewritten to point at the nearest ancestor that
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
As a special case for the date-type fields, you may specify a format for
the date by adding `:` followed by date format name (see the
-values the `--date` option to linkgit::git-rev-list[1] takes).
+values the `--date` option to linkgit:git-rev-list[1] takes).
EXAMPLES
conclude it with a patch submission, separating the discussion and the
beginning of the proposed commit log message with a scissors line.
+
-This can enabled by default with the configuration option mailinfo.scissors.
+This can be enabled by default with the configuration option mailinfo.scissors.
--no-scissors::
Ignore scissors lines. Useful for overriding mailinfo.scissors settings.
GIT
---
-Part of the linkgit:git[7] suite
+Part of the linkgit:git[1] suite
--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.
--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.
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
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
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.8.0/git.html[documentation for release 2.8]
+* link:v2.8.3/git.html[documentation for release 2.8.3]
* release notes for
+ link:RelNotes/2.8.3.txt[2.8.3],
+ link:RelNotes/2.8.2.txt[2.8.2],
+ link:RelNotes/2.8.1.txt[2.8.1],
link:RelNotes/2.8.0.txt[2.8].
* link:v2.7.3/git.html[documentation for release 2.7.3]
A fast-forward is a special type of <<def_merge,merge>> where you have a
<<def_revision,revision>> and you are "merging" another
<<def_branch,branch>>'s changes that happen to be a descendant of what
- you have. In such these cases, you do not make a new <<def_merge,merge>>
+ you have. In such a case, you do not make a new <<def_merge,merge>>
<<def_commit,commit>> but instead just update to his
revision. This will happen frequently on a
<<def_remote_tracking_branch,remote-tracking branch>> of a remote
--verify-signatures::
--no-verify-signatures::
- Verify that the commits being merged have good and trusted GPG signatures
- and abort the merge in case they do not.
+ Verify that the tip commit of the side branch being merged is
+ signed with a valid key, i.e. a key that has a valid uid: in the
+ default trust model, this means the signing key has been signed by
+ a trusted key. If the tip commit of the side branch is not signed
+ with a valid key, the merge is aborted.
--summary::
--no-summary::
- '%N': commit notes
endif::git-rev-list[]
- '%GG': raw verification message from GPG for a signed commit
-- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good,
- untrusted signature and "N" for no signature
+- '%G?': show "G" for a good (valid) signature, "B" for a bad signature,
+ "U" for a good signature with unknown validity and "N" for no signature
- '%GS': show the name of the signer for a signed commit
- '%GK': show the key used to sign a signed commit
- '%gD': reflog selector, e.g., `refs/stash@{1}`
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
----------------------
The details of the credential will be provided on the helper's stdin
stream. The exact format is the same as the input/output format of the
`git credential` plumbing command (see the section `INPUT/OUTPUT
-FORMAT` in linkgit:git-credential[7] for a detailed specification).
+FORMAT` in linkgit:git-credential[1] for a detailed specification).
For a `get` operation, the helper should produce a list of attributes
on stdout in the same format. A helper is free to produce a subset, or
linkgit:gitcredentials[7]
-linkgit:git-config[5] (See configuration variables `credential.*`)
+linkgit:git-config[1] (See configuration variables `credential.*`)
pass the command-line option, which can be specified multiple times,
to another command.
+`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
+ Define an "operation mode" option, only one of which in the same
+ group of "operating mode" options that share the same `int_var`
+ can be given by the user. `enum_val` is set to `int_var` when the
+ option is used, but an error is reported if other "operating mode"
+ option has already set its value to the same `int_var`.
+
The last element of the array must be `OPT_END()`.
static void trace_print_foo(const char *message)
{
- trace_print_key(&trace_foo, message);
+ trace_printf_key(&trace_foo, "%s", message);
}
------------
+
}
trace_performance(t, "frotz");
------------
+
+Bugs & Caveats
+--------------
+
+GIT_TRACE_* environment variables can be used to tell Git to show
+trace output to its standard error stream. Git can often spawn a pager
+internally to run its subcommand and send its standard output and
+standard error to it.
+
+Because GIT_TRACE_PERFORMANCE trace is generated only at the very end
+of the program with atexit(), which happens after the pager exits, it
+would not work well if you send its log to the standard error output
+and let Git spawn the pager at the same time.
+
+As a work around, you can for example use '--no-pager', or set
+GIT_TRACE_PERFORMANCE to another file descriptor which is redirected
+to stderr, or set GIT_TRACE_PERFORMANCE to a file specified by its
+absolute path.
+
+For example instead of the following command which by default may not
+print any performance information:
+
+------------
+GIT_TRACE_PERFORMANCE=2 git log -1
+------------
+
+you may want to use:
+
+------------
+GIT_TRACE_PERFORMANCE=2 git --no-pager log -1
+------------
+
+or:
+
+------------
+GIT_TRACE_PERFORMANCE=3 3>&2 git log -1
+------------
+
+or:
+
+------------
+GIT_TRACE_PERFORMANCE=/path/to/log/file git log -1
+------------
A push certificate begins with a set of header lines. After the
header and an empty line, the protocol commands follow, one per
-line. Note that the the trailing LF in push-cert PKT-LINEs is _not_
+line. Note that the trailing LF in push-cert PKT-LINEs is _not_
optional; it must be present.
Currently, the following header fields are defined:
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.8.0
+DEF_VER=v2.8.3
LF='
'
#
# 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:
#
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
--keyword=gettextln --keyword=eval_gettextln
XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl
LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H)
-LOCALIZED_SH = $(SCRIPT_SH)
+LOCALIZED_SH = $(SCRIPT_SH) git-parse-remote.sh
LOCALIZED_PERL = $(SCRIPT_PERL)
ifdef XGETTEXT_INCLUDE_TESTS
check: common-cmds.h
@if sparse; \
then \
- echo 2>&1 "Use 'make sparse' instead"; \
+ echo >&2 "Use 'make sparse' instead"; \
$(MAKE) --no-print-directory sparse; \
else \
- echo 2>&1 "Did you mean 'make test'?"; \
+ echo >&2 "Did you mean 'make test'?"; \
exit 1; \
fi
-Documentation/RelNotes/2.8.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.8.3.txt
\ No newline at end of file
strbuf_add(&path, pfx, pfx_len);
strbuf_addstr(&path, arg);
#else
- char *p;
/* don't add prefix to absolute paths, but still replace '\' by '/' */
strbuf_reset(&path);
if (is_absolute_path(arg))
else if (pfx_len)
strbuf_add(&path, pfx, pfx_len);
strbuf_addstr(&path, arg);
- for (p = path.buf + pfx_len; *p; p++)
- if (*p == '\\')
- *p = '/';
+ convert_slashes(path.buf + pfx_len);
#endif
return path.buf;
}
memcpy(header->magic, "ustar", 6);
memcpy(header->version, "00", 2);
- snprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header));
+ xsnprintf(header->chksum, sizeof(header->chksum), "%07o", ustar_header_chksum(header));
}
static int write_extended_header(struct archiver_args *args,
const char *pattern;
int patternlen;
int nowildcardlen;
- int flags; /* EXC_FLAG_* */
+ unsigned flags; /* EXC_FLAG_* */
};
/*
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;
+}
*/
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
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);
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;
}
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)
{
/*
linenr++;
used = parse_binary(buffer + hd + llen,
size - hd - llen, patch);
+ if (used < 0)
+ return -1;
if (used)
patchsize = used + llen;
else
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)) {
unsigned mode;
struct strbuf msg = STRBUF_INIT;
+ read_cache();
time(&now);
commit = alloc_commit_node();
commit->object.parsed = 1;
#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]"),
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
strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
state.branch);
else if (state.detached_from) {
- /* TRANSLATORS: make sure these match _("HEAD detached at ")
- and _("HEAD detached from ") in wt-status.c */
if (state.detached_at)
+ /* TRANSLATORS: make sure this matches
+ "HEAD detached at " in wt-status.c */
strbuf_addf(&desc, _("(HEAD detached at %s)"),
state.detached_from);
else
+ /* TRANSLATORS: make sure this matches
+ "HEAD detached from " in wt-status.c */
strbuf_addf(&desc, _("(HEAD detached from %s)"),
state.detached_from);
}
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();
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),
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" */
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);
BRANCH_TRACK_EXPLICIT),
OPT_SET_INT( 0, "set-upstream", &track, N_("change upstream info"),
BRANCH_TRACK_OVERRIDE),
- OPT_STRING('u', "set-upstream-to", &new_upstream, "upstream", "change the upstream info"),
+ OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
OPT_BOOL(0, "unset-upstream", &unset_upstream, "Unset the upstream info"),
OPT__COLOR(&branch_use_color, N_("use colored output")),
OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"),
if (argc == 1 && track == BRANCH_TRACK_OVERRIDE &&
!branch_existed && remote_tracking) {
fprintf(stderr, _("\nIf you wanted to make '%s' track '%s', do this:\n\n"), head, branch->name);
- fprintf(stderr, _(" git branch -d %s\n"), branch->name);
- fprintf(stderr, _(" git branch --set-upstream-to %s\n"), branch->name);
+ fprintf(stderr, " git branch -d %s\n", branch->name);
+ fprintf(stderr, " git branch --set-upstream-to %s\n", branch->name);
}
} else
}
}
- if (message.len) {
+ if (have_option_m) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) {
&sb, &ctx);
hook_arg1 = "message";
} else if (!stat(git_path_merge_msg(), &statbuf)) {
+ /*
+ * prepend SQUASH_MSG here if it exists and a
+ * "merge --squash" was originally performed
+ */
+ if (!stat(git_path_squash_msg(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
+ die_errno(_("could not read SQUASH_MSG"));
+ hook_arg1 = "squash";
+ } else
+ hook_arg1 = "merge";
if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0)
die_errno(_("could not read MERGE_MSG"));
- hook_arg1 = "merge";
} else if (!stat(git_path_squash_msg(), &statbuf)) {
if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
die_errno(_("could not read SQUASH_MSG"));
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;
static int get_urlmatch(const char *var, const char *url)
{
+ int ret;
char *section_tail;
struct string_list_item *item;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
git_config_with_options(urlmatch_config_entry, &config,
&given_config_source, respect_includes);
+ ret = !values.nr;
+
for_each_string_list_item(item, &values) {
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf buf = STRBUF_INIT;
free(config.url.url);
free((void *)config.section);
- return 0;
+ return ret;
}
static char *default_user_config(void)
struct ref *ref;
struct object_id oid;
- if (!get_oid_hex(name, &oid) && name[GIT_SHA1_HEXSZ] == ' ')
- name += GIT_SHA1_HEXSZ + 1;
- else
+ if (!get_oid_hex(name, &oid)) {
+ if (name[GIT_SHA1_HEXSZ] == ' ') {
+ /* <sha1> <ref>, find refname */
+ name += GIT_SHA1_HEXSZ + 1;
+ } else if (name[GIT_SHA1_HEXSZ] == '\0') {
+ ; /* <sha1>, leave sha1 as name */
+ } else {
+ /* <ref>, clear cruft from oid */
+ oidclr(&oid);
+ }
+ } else {
+ /* <ref>, clear cruft from get_oid_hex */
oidclr(&oid);
+ }
ref = alloc_ref(name);
oidcpy(&ref->old_oid, &oid);
}
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
- int exc_std)
+ int exc_std, int use_index)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
+ if (!use_index)
+ dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
setup_standard_excludes(&dir);
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
if (list.nr)
die(_("--no-index or --untracked cannot be used with revs."));
- hit = grep_directory(&opt, &pathspec, use_exclude);
+ hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
} else if (0 <= opt_exclude) {
die(_("--[no-]exclude-standard cannot be used for tracked contents."));
} else if (!list.nr) {
nr_unresolved * sizeof(*objects));
f = sha1fd(output_fd, curr_pack);
fix_unresolved_deltas(f);
- strbuf_addf(&msg, _("completed with %d local objects"),
+ strbuf_addf(&msg, Q_("completed with %d local object",
+ "completed with %d local objects",
+ nr_objects - nr_objects_initial),
nr_objects - nr_objects_initial);
stop_progress_msg(&progress, msg.buf);
strbuf_release(&msg);
}
}
+static const char *derive_filename(const char *pack_name, const char *suffix,
+ struct strbuf *buf)
+{
+ size_t len;
+ if (!strip_suffix(pack_name, ".pack", &len))
+ die(_("packfile name '%s' does not end with '.pack'"),
+ pack_name);
+ strbuf_add(buf, pack_name, len);
+ strbuf_addstr(buf, suffix);
+ return buf->buf;
+}
+
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
usage(index_pack_usage);
if (fix_thin_pack && !from_stdin)
die(_("--fix-thin cannot be used without --stdin"));
- if (!index_name && pack_name) {
- size_t len;
- if (!strip_suffix(pack_name, ".pack", &len))
- die(_("packfile name '%s' does not end with '.pack'"),
- pack_name);
- strbuf_add(&index_name_buf, pack_name, len);
- strbuf_addstr(&index_name_buf, ".idx");
- index_name = index_name_buf.buf;
- }
- if (keep_msg && !keep_name && pack_name) {
- size_t len;
- if (!strip_suffix(pack_name, ".pack", &len))
- die(_("packfile name '%s' does not end with '.pack'"),
- pack_name);
- strbuf_add(&keep_name_buf, pack_name, len);
- strbuf_addstr(&keep_name_buf, ".idx");
- keep_name = keep_name_buf.buf;
- }
+ if (!index_name && pack_name)
+ index_name = derive_filename(pack_name, ".idx", &index_name_buf);
+ if (keep_msg && !keep_name && pack_name)
+ keep_name = derive_filename(pack_name, ".keep", &keep_name_buf);
+
if (verify) {
if (!index_name)
die(_("--verify with no packfile name given"));
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;
/* 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;
}
/* 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"));
set_git_dir(real_path(git_dir));
git_link = NULL;
}
+ startup_info->have_repository = 1;
return 0;
}
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
* 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");
"", 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] != '/' ? "/" : "");
}
* 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:
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;
}
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
builtin_merge_options);
if (!head_commit) {
- struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
* We do the same for "git pull".
*/
+ unsigned char *remote_head_sha1;
if (squash)
die(_("Squash commit into empty head not supported yet"));
if (fast_forward == FF_NO)
"an empty head"));
remoteheads = collect_parents(head_commit, &head_subsumed,
argc, argv, NULL);
- remote_head = remoteheads->item;
- if (!remote_head)
+ if (!remoteheads)
die(_("%s - not something we can merge"), argv[0]);
if (remoteheads->next)
die(_("Can merge only exactly one commit into empty head"));
- read_empty(remote_head->object.oid.hash, 0);
- update_ref("initial pull", "HEAD", remote_head->object.oid.hash,
+ remote_head_sha1 = remoteheads->item->object.oid.hash;
+ read_empty(remote_head_sha1, 0);
+ update_ref("initial pull", "HEAD", remote_head_sha1,
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
}
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)
typedef struct rev_name {
const char *tip_name;
+ unsigned long taggerdate;
int generation;
int distance;
} rev_name;
#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;
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
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);
}
}
}
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;
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;
}
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;
}
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;
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) {
fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
fprintf_ln(stderr, _("See git-pull(1) for details."));
fprintf(stderr, "\n");
- fprintf_ln(stderr, " git pull <remote> <branch>");
+ fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>"));
fprintf(stderr, "\n");
} else if (!curr_branch->merge_nr) {
const char *remote_name = NULL;
if (for_each_remote(get_only_remote, &remote_name) || !remote_name)
- remote_name = "<remote>";
+ remote_name = _("<remote>");
fprintf_ln(stderr, _("There is no tracking information for the current branch."));
if (opt_rebase)
fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
fprintf_ln(stderr, _("See git-pull(1) for details."));
fprintf(stderr, "\n");
- fprintf_ln(stderr, " git pull <remote> <branch>");
+ fprintf_ln(stderr, " git pull %s %s", _("<remote>"), _("<branch>"));
fprintf(stderr, "\n");
- fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n"
- "\n"
- " git branch --set-upstream-to=%s/<branch> %s\n"),
- remote_name, curr_branch->name);
+ fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:"));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git branch --set-upstream-to=%s/%s %s\n",
+ remote_name, _("<branch>"), curr_branch->name);
} else
fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
"from the remote, but no such ref was fetched."),
size_t i;
if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
if (ferror(stdin))
- die("Comammand input error");
+ die("Command input error");
exit(0);
}
/* Strip end of line characters. */
url_nr = states.remote->url_nr;
}
for (i = 0; i < url_nr; i++)
+ /* TRANSLATORS: the colon ':' should align with
+ the one in " Fetch URL: %s" translation */
printf_ln(_(" Push URL: %s"), url[i]);
if (!i)
printf_ln(_(" Push URL: %s"), "(no URL)");
};
check_replace_refs = 0;
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+ int did_repo_setup = 0;
int has_dashdash = 0;
int output_prefix = 0;
unsigned char sha1[20];
}
}
- prefix = setup_git_directory();
- git_config(git_default_config, NULL);
+ /* No options; just report on whether we're in a git repo or not. */
+ if (argc == 1) {
+ setup_git_directory();
+ git_config(git_default_config, NULL);
+ return 0;
+ }
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ if (!strcmp(arg, "--local-env-vars")) {
+ int i;
+ for (i = 0; local_repo_env[i]; i++)
+ printf("%s\n", local_repo_env[i]);
+ continue;
+ }
+ if (!strcmp(arg, "--resolve-git-dir")) {
+ const char *gitdir = argv[++i];
+ if (!gitdir)
+ die("--resolve-git-dir requires an argument");
+ gitdir = resolve_gitdir(gitdir);
+ if (!gitdir)
+ die("not a gitdir '%s'", argv[i]);
+ puts(gitdir);
+ continue;
+ }
+
+ /* The rest of the options require a git repository. */
+ if (!did_repo_setup) {
+ prefix = setup_git_directory();
+ git_config(git_default_config, NULL);
+ did_repo_setup = 1;
+ }
+
if (!strcmp(arg, "--git-path")) {
if (!argv[i + 1])
die("--git-path requires an argument");
add_ref_exclusion(&ref_excludes, arg + 10);
continue;
}
- if (!strcmp(arg, "--local-env-vars")) {
- int i;
- for (i = 0; local_repo_env[i]; i++)
- printf("%s\n", local_repo_env[i]);
- continue;
- }
if (!strcmp(arg, "--show-toplevel")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir()));
continue;
}
- if (!strcmp(arg, "--resolve-git-dir")) {
- const char *gitdir = argv[++i];
- if (!gitdir)
- die("--resolve-git-dir requires an argument");
- gitdir = resolve_gitdir(gitdir);
- if (!gitdir)
- die("not a gitdir '%s'", argv[i]);
- puts(gitdir);
- continue;
- }
if (!strcmp(arg, "--is-inside-git-dir")) {
printf("%s\n", is_inside_git_dir() ? "true"
: "false");
list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
if (list.entry[list.nr++].is_submodule &&
!is_staging_gitmodules_ok())
- die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
+ die (_("Please stage your changes to .gitmodules or stash them to proceed"));
}
if (pathspec.nr) {
* --all and --mirror are incompatible; neither makes sense
* with any refspecs.
*/
- if ((refspecs && (send_all || args.send_mirror)) ||
+ if ((nr_refspecs > 0 && (send_all || args.send_mirror)) ||
(send_all && args.send_mirror))
usage_with_options(send_pack_usage, options);
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;
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
+ if (!path || !*path)
+ die(_("submodule--helper: unspecified or empty --path"));
+
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)
}
/* 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;
}
/* 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);
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)
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;
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
extern char comment_line_char;
extern int auto_comment_line_char;
+/* Windows only */
+enum hide_dotfiles_type {
+ HIDE_DOTFILES_FALSE = 0,
+ HIDE_DOTFILES_TRUE,
+ HIDE_DOTFILES_DOTGITONLY
+};
+extern enum hide_dotfiles_type hide_dotfiles;
+
enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0,
*/
#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
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);
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 *);
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);
/* Takes a negative value returned by split_cmdline */
const char *split_cmdline_strerror(int cmdline_errno);
-/* git.c */
+/* setup.c */
struct startup_info {
int have_repository;
const char *prefix;
--- /dev/null
+#!/bin/sh
+#
+# Perform sanity checks on documentation and build it.
+#
+
+set -e
+
+make check-builtins
+make check-docs
+make doc
+
+test -s Documentation/git.html
+test -s Documentation/git.xml
+test -s Documentation/git.1
#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>
return ret;
}
+static inline int needs_hiding(const char *path)
+{
+ const char *basename;
+
+ if (hide_dotfiles == HIDE_DOTFILES_FALSE)
+ return 0;
+
+ /* We cannot use basename(), as it would remove trailing slashes */
+ mingw_skip_dos_drive_prefix((char **)&path);
+ if (!*path)
+ return 0;
+
+ for (basename = path; *path; path++)
+ if (is_dir_sep(*path)) {
+ do {
+ path++;
+ } while (is_dir_sep(*path));
+ /* ignore trailing slashes */
+ if (*path)
+ basename = path;
+ }
+
+ if (hide_dotfiles == HIDE_DOTFILES_TRUE)
+ return *basename == '.';
+
+ assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY);
+ return !strncasecmp(".git", basename, 4) &&
+ (!basename[4] || is_dir_sep(basename[4]));
+}
+
+static int set_hidden_flag(const wchar_t *path, int set)
+{
+ DWORD original = GetFileAttributesW(path), modified;
+ if (set)
+ modified = original | FILE_ATTRIBUTE_HIDDEN;
+ else
+ modified = original & ~FILE_ATTRIBUTE_HIDDEN;
+ if (original == modified || SetFileAttributesW(path, modified))
+ return 0;
+ errno = err_win_to_posix(GetLastError());
+ return -1;
+}
+
int mingw_mkdir(const char *path, int mode)
{
int ret;
if (xutftowcs_path(wpath, path) < 0)
return -1;
ret = _wmkdir(wpath);
+ if (!ret && needs_hiding(path))
+ return set_hidden_flag(wpath, 1);
return ret;
}
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
errno = EISDIR;
}
+ if ((oflags & O_CREAT) && needs_hiding(filename)) {
+ /*
+ * Internally, _wopen() uses the CreateFile() API which errors
+ * out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was
+ * specified and an already existing file's attributes do not
+ * match *exactly*. As there is no mode or flag we can set that
+ * would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try
+ * again *without* the O_CREAT flag (that corresponds to the
+ * CREATE_ALWAYS flag of CreateFile()).
+ */
+ if (fd < 0 && errno == EACCES)
+ fd = _wopen(wfilename, oflags & ~O_CREAT, mode);
+ if (fd >= 0 && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
+ }
return fd;
}
#undef fopen
FILE *mingw_fopen (const char *filename, const char *otype)
{
+ int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null"))
if (xutftowcs_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL;
+ if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+ error("could not unhide %s", filename);
+ return NULL;
+ }
file = _wfopen(wfilename, wotype);
+ if (file && hide && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
return file;
}
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
{
+ int hide = needs_hiding(filename);
FILE *file;
wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null"))
if (xutftowcs_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL;
+ if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
+ error("could not unhide %s", filename);
+ return NULL;
+ }
file = _wfreopen(wfilename, wotype, stream);
+ if (file && hide && set_hidden_flag(wfilename, 1))
+ warning("could not mark '%s' as hidden.", filename);
return file;
}
char *mingw_getcwd(char *pointer, int len)
{
- int i;
wchar_t wpointer[MAX_PATH];
if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
return NULL;
if (xwcstoutf(pointer, wpointer, len) < 0)
return NULL;
- for (i = 0; pointer[i]; i++)
- if (pointer[i] == '\\')
- pointer[i] = '/';
+ convert_slashes(pointer);
return pointer;
}
* executable (by not mistaking the dir separators
* for escape characters).
*/
- for (; *tmp; tmp++)
- if (*tmp == '\\')
- *tmp = '/';
+ convert_slashes(tmp);
}
/* simulate TERM to enable auto-color (see color.c) */
#define sigemptyset(x) (void)0
static inline int sigaddset(sigset_t *set, int signum)
{ return 0; }
+#define SIG_BLOCK 0
#define SIG_UNBLOCK 0
static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{ return 0; }
int mingw_offset_1st_component(const char *path);
#define offset_1st_component mingw_offset_1st_component
#define PATH_SEP ';'
-#ifndef __MINGW64_VERSION_MAJOR
+#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
#define PRId64 "I64d"
#else
void mingw_open_html(const char *path);
#define open_html mingw_open_html
-void mingw_mark_as_git_dir(const char *dir);
-#define mark_as_git_dir mingw_mark_as_git_dir
-
/**
* Converts UTF-8 encoded string to UTF-16LE.
*
if (errno || inleft) {
/*
* iconv() failed and errno could be E2BIG, EILSEQ, EINVAL, EBADF
- * MacOS X avoids illegal byte sequemces.
+ * MacOS X avoids illegal byte sequences.
* If they occur on a mounted drive (e.g. NFS) it is not worth to
* die() for that, but rather let the user see the original name
*/
* always have room for a trailing NUL byte.
*/
#ifndef SNPRINTF_SIZE_CORR
-#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900)
#define SNPRINTF_SIZE_CORR 1
#else
#define SNPRINTF_SIZE_CORR 0
typedef int64_t off64_t;
+#if !defined(_MSC_VER) || _MSC_VER < 1600
#define INTMAX_MIN _I64_MIN
#define INTMAX_MAX _I64_MAX
#define UINTMAX_MAX _UI64_MAX
#define UINT32_MAX 0xffffffff /* 4294967295U */
+#else
+#include <stdint.h>
+#endif
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
return TlsGetValue(key);
}
+#ifndef __MINGW64_VERSION_MAJOR
+static inline int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
+{
+ return 0;
+}
+#endif
+
#endif /* PTHREAD_H */
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)
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;
/*
return 0;
}
+ if (!strcmp(var, "core.hidedotfiles")) {
+ if (value && !strcasecmp(value, "dotgitonly"))
+ hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
+ else
+ hide_dotfiles = git_config_bool(var, value);
+ return 0;
+ }
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
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;
}
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.");
}
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(),
free(xdg_config);
free(user_config);
+ free(repo_config);
return ret == 0 ? found : ret;
}
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) {
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)
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));
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,
#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,
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
[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],
+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
=============
+Contributing
+============
+
git-multimail is an open-source project, built by volunteers. We would
welcome your help!
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
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
-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
community.)
+Troubleshooting/FAQ
+-------------------
+
+Please read `<doc/troubleshooting.rst>`__ for frequently asked
+questions and common issues with git-multimail.
+
+
Configuration
-------------
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).
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.
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.
* 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
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
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
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
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;
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
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:
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
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
- 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
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
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
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
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
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'.
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
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
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
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
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.
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.
--- /dev/null
+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.
--- /dev/null
+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>``).
#! /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
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
if PYTHON3:
+ def is_string(s):
+ return isinstance(s, str)
+
def str_to_bytes(s):
return s.encode(ENCODING)
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
"""
+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
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(
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.
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):
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
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)
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
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
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)
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):
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
):
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):
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()
):
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):
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:
smtpservertimeout=10.0, smtpserverdebuglevel=0,
smtpencryption='none',
smtpuser='', smtppass='',
+ smtpcacerts=''
):
if not envelopesender:
sys.stderr.write(
self.security = smtpencryption
self.username = smtpuser
self.password = smtppass
+ self.smtpcacerts = smtpcacerts
try:
def call(klass, server, timeout):
try:
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. ***')
def __del__(self):
if hasattr(self, 'smtp'):
self.smtp.quit()
+ del self.smtp
def send(self, lines, to_addrs):
try:
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)
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.
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']
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,))()
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:
['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,
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):
]
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):
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,
smtpencryption=smtpencryption,
smtpuser=smtpuser,
smtppass=smtppass,
+ smtpcacerts=smtpcacerts
)
elif mailer == 'sendmail':
command = config.get('sendmailcommand')
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')
# 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:
fprintf(out, "password=%s\n", e->item.password);
}
}
- else if (!strcmp(action.buf, "exit"))
+ else if (!strcmp(action.buf, "exit")) {
+ /*
+ * It's important that we clean up our socket first, and then
+ * signal the client only once we have finished the cleanup.
+ * Calling exit() directly does this, because we clean up in
+ * our atexit() handler, and then signal the client when our
+ * process actually ends, which closes the socket and gives
+ * them EOF.
+ */
exit(0);
+ }
else if (!strcmp(action.buf, "erase"))
remove_credential(&c);
else if (!strcmp(action.buf, "store")) {
write_or_die(1, in, r);
got_data = 1;
}
+ close(fd);
return got_data;
}
int i, renames = 0;
struct hashmap file_table;
- /* Add all sources to the hash table */
+ /* Add all sources to the hash table in reverse order, because
+ * later on they will be retrieved in LIFO order.
+ */
hashmap_init(&file_table, NULL, rename_src_nr);
- for (i = 0; i < rename_src_nr; i++)
+ for (i = rename_src_nr-1; i >= 0; i--)
insert_file_table(&file_table, i, rename_src[i].p->one);
/* Walk the destinations and find best source match */
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)
void parse_exclude_pattern(const char **pattern,
int *patternlen,
- int *flags,
+ unsigned *flags,
int *nowildcardlen)
{
const char *p = *pattern;
{
struct exclude *x;
int patternlen;
- int flags;
+ unsigned flags;
int nowildcardlen;
parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
int match_basename(const char *basename, int basenamelen,
const char *pattern, int prefix, int patternlen,
- int flags)
+ unsigned flags)
{
if (prefix == patternlen) {
if (patternlen == basenamelen &&
int match_pathname(const char *pathname, int pathlen,
const char *base, int baselen,
const char *pattern, int prefix, int patternlen,
- int flags)
+ unsigned flags)
{
const char *name;
int namelen;
int nowildcardlen;
const char *base;
int baselen;
- int flags;
+ unsigned flags; /* EXC_FLAG_* */
/*
* Counting starts from 1 for line numbers in ignore files,
* attr.c:path_matches()
*/
extern int match_basename(const char *, int,
- const char *, int, int, int);
+ const char *, int, int, unsigned);
extern int match_pathname(const char *, int,
const char *, int,
- const char *, int, int, int);
+ const char *, int, int, unsigned);
extern struct exclude *last_exclude_matching(struct dir_struct *dir,
const char *name, int *dtype);
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
struct exclude_list *el, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
-extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
+extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *el, int srcpos);
extern void clear_exclude_list(struct exclude_list *el);
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.
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;
int core_apply_sparse_checkout;
int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
-struct startup_info *startup_info;
unsigned long pack_size_limit_cfg;
+enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
#ifndef PROTECT_HFS_DEFAULT
#define PROTECT_HFS_DEFAULT 0
{
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;
+}
#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;
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;
}
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");
FUNC(HAS_DOTGIT, WARN) \
FUNC(NULL_SHA1, WARN) \
FUNC(ZERO_PADDED_FILEMODE, WARN) \
+ FUNC(NUL_IN_COMMIT, WARN) \
/* infos (reported as warnings, but ignored by default) */ \
FUNC(BAD_TAG_NAME, INFO) \
FUNC(MISSING_TAGGER_ENTRY, INFO)
struct commit_graft *graft;
unsigned parent_count, parent_line_count = 0, author_count;
int err;
+ const char *buffer_begin = buffer;
if (verify_headers(buffer, size, &commit->object, options))
return -1;
err = fsck_ident(&buffer, &commit->object, options);
if (err)
return err;
- if (!commit->tree)
- return report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
-
+ if (!commit->tree) {
+ err = report(options, &commit->object, FSCK_MSG_BAD_TREE, "could not load commit's tree %s", sha1_to_hex(tree_sha1));
+ if (err)
+ return err;
+ }
+ if (memchr(buffer_begin, '\0', size)) {
+ err = report(options, &commit->object, FSCK_MSG_NUL_IN_COMMIT,
+ "NUL byte in the commit object body");
+ if (err)
+ return err;
+ }
return 0;
}
#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
# FUTURE: This would more accurately emulate CVS by sending
# another copy of sticky after processing the files in that
# directory. Or intermediate: perhaps send all sticky's for
- # $seendirs after after processing all files.
+ # $seendirs after processing all files.
}
# update \n
}
# Return working directory CVS revision "1.X" out
-# of the the working directory "entries" state, for the given filename.
+# of the working directory "entries" state, for the given filename.
# This is prefixed with a dash if the file is scheduled for removal
# when it is committed.
sub revparse
return $filename;
}
-# Remove prependdir from the path, so that is is relative to the directory
+# Remove prependdir from the path, so that it is relative to the directory
# the CVS client was started from, rather than the top of the project.
# Essentially the inverse of filecleanup().
sub remove_prependdir
"$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
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 ;;
case "$ans" in
[mMcC]*)
git add -- "$MERGED"
- cleanup_temp_files --save-backup
+ if test "$merge_keep_backup" = "true"
+ then
+ cleanup_temp_files --save-backup
+ else
+ cleanup_temp_files
+ fi
return 0
;;
[dD]*)
return 0
;;
[aA]*)
+ if test "$merge_keep_temporaries" = "false"
+ then
+ cleanup_temp_files
+ fi
return 1
;;
esac
return
fi
- mv -- "$MERGED" "$BACKUP"
- cp -- "$BACKUP" "$MERGED"
+ if test -f "$MERGED"
+ then
+ mv -- "$MERGED" "$BACKUP"
+ cp -- "$BACKUP" "$MERGED"
+ fi
+ # Create a parent directory to handle delete/delete conflicts
+ # where the base's directory no longer exists.
+ mkdir -p "$(dirname "$MERGED")"
checkout_staged_file 1 "$MERGED" "$BASE"
checkout_staged_file 2 "$MERGED" "$LOCAL"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
resolve_deleted_merge
- return
+ status=$?
+ rmdir -p "$(dirname "$MERGED")" 2>/dev/null
+ return $status
fi
if is_symlink "$local_mode" || is_symlink "$remote_mode"
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]*)
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],
)
# 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(
error_on_missing_default_upstream () {
cmd="$1"
op_type="$2"
- op_prep="$3"
+ op_prep="$3" # FIXME: op_prep is no longer used
example="$4"
branch_name=$(git symbolic-ref -q HEAD)
+ display_branch_name="${branch_name#refs/heads/}"
# If there's only one remote, use that in the suggestion
- remote="<remote>"
+ remote="$(gettext "<remote>")"
+ branch="$(gettext "<branch>")"
if test $(git remote | wc -l) = 1
then
remote=$(git remote)
if test -z "$branch_name"
then
- echo "You are not currently on a branch. Please specify which
-branch you want to $op_type $op_prep. See git-${cmd}(1) for details.
-
- $example
-"
+ gettextln "You are not currently on a branch."
else
- echo "There is no tracking information for the current branch.
-Please specify which branch you want to $op_type $op_prep.
-See git-${cmd}(1) for details
-
- $example
-
-If you wish to set tracking information for this branch you can do so with:
-
- git branch --set-upstream-to=$remote/<branch> ${branch_name#refs/heads/}
-"
+ gettextln "There is no tracking information for the current branch."
+ fi
+ case "$op_type" in
+ rebase)
+ gettextln "Please specify which branch you want to rebase against."
+ ;;
+ merge)
+ gettextln "Please specify which branch you want to merge with."
+ ;;
+ *)
+ echo >&2 "BUG: unknown operation type: $op_type"
+ exit 1
+ ;;
+ esac
+ eval_gettextln "See git-\${cmd}(1) for details."
+ echo
+ echo " $example"
+ echo
+ if test -n "$branch_name"
+ then
+ gettextln "If you wish to set tracking information for this branch you can do so with:"
+ echo
+ echo " git branch --set-upstream-to=$remote/$branch $display_branch_name"
+ echo
fi
exit 1
}
cr=$(printf "\015")
strategy_args=${strategy:+--strategy=$strategy}
+test -n "$strategy_opts" &&
eval '
for strategy_opt in '"$strategy_opts"'
do
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);
$aliases{$alias} = \@addr
}}},
mailrc => sub { my $fh = shift; while (<$fh>) {
- if (/^alias\s+(\S+)\s+(.*)$/) {
+ if (/^alias\s+(\S+)\s+(.*?)\s*$/) {
# spaces delimit multiple addresses
$aliases{$1} = [ quotewords('\s+', 0, $2) ];
}}},
# 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
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);
}
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++;
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
}
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/"
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
}
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")"
;;
!*)
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
;;
*)
(
prefix="$displaypath/"
clear_local_git_env
+ wt_prefix=
cd "$sm_path" &&
eval cmd_status
) ||
"concept guides. See 'git help <command>' or 'git help <concept>'\n"
"to read about a specific subcommand or concept.");
-static struct startup_info git_startup_info;
static int use_pager = -1;
static char *orig_cwd;
static const char *env_names[] = {
const char *cmd;
int done_help = 0;
- startup_info = &git_startup_info;
-
cmd = git_extract_argv0_path(argv[0]);
if (!cmd)
cmd = "git-help";
%files -f bin-man-doc-files
%defattr(-,root,root)
%{_datadir}/git-core/
-%doc README COPYING Documentation/*.txt
+%doc README.md COPYING Documentation/*.txt
%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
%{!?_without_docs: %doc Documentation/technical}
%{_sysconfdir}/bash_completion.d
close $fd;
open $fd, quote_command(git_cmd(), "cat-file", "blob", $hash)." | ".
+ quote_command($^X, '-CO', '-MEncode=decode,FB_DEFAULT', '-pse',
+ '$_ = decode($fe, $_, FB_DEFAULT) if !utf8::decode($_);',
+ '--', "-fe=$fallback_encoding")." | ".
quote_command($highlight_bin).
" --replace-tabs=8 --fragment --syntax $syntax |"
or die_error(500, "Couldn't open file or run syntax highlighter");
return git_config_string(&http_proxy_authmethod, var, value);
if (!strcmp("http.cookiefile", var))
- return git_config_string(&curl_cookie_file, var, value);
+ return git_config_pathname(&curl_cookie_file, var, value);
if (!strcmp("http.savecookies", var)) {
curl_save_cookies = git_config_bool(var, value);
return 0;
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"))
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;
}
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);
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);
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;
(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++) {
/* 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,
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;
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");
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
- strbuf_addf(buf, "Unable to create '%s.lock': %s.\n\n"
- "If no other git process is currently running, this probably means a\n"
- "git process crashed in this repository earlier. Make sure no other git\n"
- "process is running and remove the file manually to continue.",
+ strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n"
+ "Another git process seems to be running in this repository, e.g.\n"
+ "an editor opened by 'git commit'. Please make sure all processes\n"
+ "are terminated then try again. If it still fails, a git process\n"
+ "may have crashed in this repository earlier:\n"
+ "remove the file manually to continue."),
absolute_path(path), strerror(err));
} else
- strbuf_addf(buf, "Unable to create '%s.lock': %s",
+ strbuf_addf(buf, _("Unable to create '%s.lock': %s"),
absolute_path(path), strerror(err));
}
git_mailmap_blob = "HEAD:.mailmap";
err |= read_mailmap_file(map, ".mailmap", repo_abbrev);
- err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev);
+ if (startup_info->have_repository)
+ err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev);
err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev);
return err;
}
* definite
* definition
*
- * The trie would look look like:
+ * The trie would look like:
* root: len = 0, children a and d non-NULL, value = NULL.
* a: len = 2, contents = bc, value = (data for "abc")
* d: len = 2, contents = ef, children i non-NULL, value = (data for "def")
{
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;
{
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;
Execute the given C<COMMAND> in the same way as command_output_pipe()
does but return both an input pipe filehandle and an output pipe filehandle.
-The function will return return C<($pid, $pipe_in, $pipe_out, $ctx)>.
+The function will return C<($pid, $pipe_in, $pipe_out, $ctx)>.
See C<command_close_bidi_pipe()> for details.
=cut
#: builtin/am.c:2321 builtin/commit.c:1593 builtin/merge.c:225
#: builtin/pull.c:159 builtin/revert.c:92 builtin/tag.c:355
msgid "key-id"
-msgstr "id de clé"
+msgstr "id-clé"
#: builtin/am.c:2322
msgid "GPG-sign commits"
#: builtin/checkout.c:1154
msgid "conflict style (merge or diff3)"
-msgstr "style de conflit (fusion ou diff3)"
+msgstr "style de conflit (merge (fusion) ou diff3)"
#: builtin/checkout.c:1157
msgid "do not limit pathspecs to sparse entries only"
#: builtin/fetch.c:122 builtin/log.c:1236
msgid "dir"
-msgstr "dir"
+msgstr "répertoire"
#: builtin/fetch.c:123
msgid "prepend this to submodule path output"
#: builtin/show-ref.c:165
msgid "only show tags (can be combined with heads)"
-msgstr "afficher seulement les étiquettes (peut être combiné avec des têtes)"
+msgstr "afficher seulement les étiquettes (peut être combiné avec heads)"
#: builtin/show-ref.c:166
msgid "only show heads (can be combined with tags)"
-msgstr "afficher seulement les têtes (peut être combiné avec des étiquettes)"
+msgstr "afficher seulement les têtes (peut être combiné avec tags)"
#: builtin/show-ref.c:167
msgid "stricter reference checking, requires exact ref path"
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,
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;
{
static int loaded;
struct object_id oid;
- const char *head_ref;
int flag;
if (loaded)
loaded = 1;
current_branch = NULL;
- head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag);
- if (head_ref && (flag & REF_ISSYMREF) &&
- skip_prefix(head_ref, "refs/heads/", &head_ref)) {
- current_branch = make_branch(head_ref, 0);
+ if (startup_info->have_repository) {
+ const char *head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag);
+ if (head_ref && (flag & REF_ISSYMREF) &&
+ skip_prefix(head_ref, "refs/heads/", &head_ref)) {
+ current_branch = make_branch(head_ref, 0);
+ }
}
git_config(handle_config, NULL);
alias_all_urls();
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) {
"Your branch and '%s' have diverged,\n"
"and have %d and %d different commits each, "
"respectively.\n",
- theirs),
+ ours + theirs),
base, ours, theirs);
if (advice_status_hints)
strbuf_addf(sb,
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;
int proc_in;
int proc_out;
#endif
+ int isolate_sigpipe;
};
int start_async(struct async *async);
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;
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;
}
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)
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;
/*
* The input parameter must contain an absolute path, and it must already be
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)) {
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,
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;
}
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;
}
/*
else
setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- if (startup_info) {
- startup_info->have_repository = !nongit_ok || !*nongit_ok;
- startup_info->prefix = prefix;
- }
+ startup_info->have_repository = !nongit_ok || !*nongit_ok;
+ startup_info->prefix = prefix;
+
return prefix;
}
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)
{
- return check_repository_format_gently(get_git_dir(), NULL);
+ check_repository_format_gently(get_git_dir(), NULL);
+ startup_info->have_repository = 1;
}
/*
if (!starts_with(rel, "./") && !starts_with(rel, "../"))
return NULL;
- if (!startup_info)
- die("BUG: startup_info struct is not initialized.");
-
if (!is_inside_work_tree())
die("relative path syntax can't be used outside working tree.");
* To keep track of the shared entries between
* istate->base->cache[] and istate->cache[], base entry
* position is stored in each base entry. All positions start
- * from 1 instead of 0, which is resrved to say "this is a new
+ * from 1 instead of 0, which is reserved to say "this is a new
* entry".
*/
for (i = 0; i < base->cache_nr; i++)
if (errno == ENOMEM)
die("Out of memory, getdelim failed");
- /* Restore slopbuf that we moved out of the way before */
+ /*
+ * Restore strbuf invariants; if getdelim left us with a NULL pointer,
+ * we can just re-init, but otherwise we should make sure that our
+ * length is empty, and that the result is NUL-terminated.
+ */
if (!sb->buf)
strbuf_init(sb, 0);
+ else
+ strbuf_reset(sb);
return EOF;
}
#else
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;
}
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,
struct hashmap_iter iter;
struct submodule_entry *entry;
- hashmap_iter_init(&cache->for_name, &iter);
- entry = hashmap_iter_next(&iter);
+ entry = hashmap_iter_first(&cache->for_name, &iter);
if (!entry)
return NULL;
return entry->config;
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;
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;
}
-x::
Turn on shell tracing (i.e., `set -x`) during the tests
- themselves. Implies `--verbose`. Note that this can cause
- failures in some tests which redirect and test the
- output of shell functions. Use with caution.
+ themselves. Implies `--verbose`. Note that in non-bash shells,
+ this can cause failures in some tests which redirect and test
+ the output of shell functions. Use with caution.
-d::
--debug::
# 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())'
+ (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))')
}
# Try to pick a unique port: guess a large number, then hope
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"
}
PassEnv GNUPGHOME
PassEnv ASAN_OPTIONS
PassEnv GIT_TRACE
+PassEnv GIT_CONFIG_NOSYSTEM
Alias /dumb/ www/
Alias /auth/dumb/ www/auth/dumb/
}
check_sub_test_lib_test_err () {
- name="$1" # stdin is the expected output output from the test
+ name="$1" # stdin is the expected output from the test
# expected error output is in descriptior 3
(
cd "$name" &&
test_path_is_dir realgitdir/refs
'
+# Tests for the hidden file attribute on windows
+is_hidden () {
+ # Use the output of `attrib`, ignore the absolute path
+ case "$(attrib "$1")" in *H*?:*) return 0;; esac
+ return 1
+}
+
+test_expect_success MINGW '.git hidden' '
+ rm -rf newdir &&
+ (
+ unset GIT_DIR GIT_WORK_TREE
+ mkdir newdir &&
+ cd newdir &&
+ git init &&
+ is_hidden .git
+ ) &&
+ check_config newdir/.git false unset
+'
+
+test_expect_success MINGW 'bare git dir not hidden' '
+ rm -rf newdir &&
+ (
+ unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
+ mkdir newdir &&
+ cd newdir &&
+ git --bare init
+ ) &&
+ ! is_hidden newdir
+'
+
test_done
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
) &&
cookieFile = /tmp/cookie.txt
EOF
+ test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
+ test_must_be_empty actual &&
+
echo true >expect &&
git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&
test_cmp expect actual &&
)
'
+test_expect_success 'NUL in commit' '
+ rm -fr nul-in-commit &&
+ git init nul-in-commit &&
+ (
+ cd nul-in-commit &&
+ git commit --allow-empty -m "initial commitQNUL after message" &&
+ git cat-file commit HEAD >original &&
+ q_to_nul <original >munged &&
+ git hash-object -w -t commit --stdin <munged >name &&
+ git branch bad $(cat name) &&
+
+ test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 &&
+ grep nulInCommit warn.1 &&
+ git fsck 2>warn.2 &&
+ grep nulInCommit warn.2
+ )
+'
+
# create a static test repo which is broken by omitting
# one particular object ($1, which is looked up via rev-parse
# in the new repository).
grep "relative path syntax can.t be used outside working tree." error
'
-test_expect_success 'relative path when startup_info is NULL' '
- test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
- grep "BUG: startup_info struct is not initialized." error
-'
-
test_expect_success '<commit>:file correctly diagnosed after a pathname' '
test_must_fail git rev-parse file.txt HEAD:file.txt 1>actual 2>error &&
test_i18ngrep ! "exists on disk" error &&
--- /dev/null
+#!/bin/sh
+
+test_description='check that certain rev-parse options work outside repo'
+. ./test-lib.sh
+
+test_expect_success 'set up non-repo directory' '
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+ mkdir non-repo &&
+ cd non-repo &&
+ # confirm that git does not find a repo
+ test_must_fail git rev-parse --git-dir
+'
+
+# Rather than directly test the output of sq-quote directly,
+# make sure the shell can read back a tricky case, since
+# that's what we really care about anyway.
+tricky="really tricky with \\ and \" and '"
+dump_args () {
+ for i in "$@"; do
+ echo "arg: $i"
+ done
+}
+test_expect_success 'rev-parse --sq-quote' '
+ dump_args "$tricky" easy >expect &&
+ eval "dump_args $(git rev-parse --sq-quote "$tricky" easy)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-parse --local-env-vars' '
+ git rev-parse --local-env-vars >actual &&
+ # we do not want to depend on the complete list here,
+ # so just look for something plausible
+ grep ^GIT_DIR actual
+'
+
+test_expect_success 'rev-parse --resolve-git-dir' '
+ git init --separate-git-dir repo dir &&
+ test_must_fail git rev-parse --resolve-git-dir . &&
+ echo "$(pwd)/repo" >expect &&
+ git rev-parse --resolve-git-dir dir/.git >actual &&
+ test_cmp expect actual
+'
+
+test_done
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' '
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)" &&
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
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 &&
git_revert () {
git status -su >expect &&
ls -1pR * >>expect &&
- tar czf "$TRASH_DIRECTORY/tmp.tgz" * &&
+ tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
git checkout "$1" &&
git revert HEAD &&
rm -rf * &&
- tar xzf "$TRASH_DIRECTORY/tmp.tgz" &&
+ tar xf "$TRASH_DIRECTORY/tmp.tar" &&
git status -su >actual &&
ls -1pR * >>actual &&
test_cmp expect actual &&
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
+test_expect_success 'two files with same basename and same content' '
+ git reset --hard &&
+ mkdir -p dir/A dir/B &&
+ cp path1 dir/A/file &&
+ cp path1 dir/B/file &&
+ git add dir &&
+ git commit -m 2 &&
+ git mv dir other-dir &&
+ git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
+'
+
test_expect_success 'setup for many rename source candidates' '
git reset --hard &&
for i in 0 1 2 3 4 5 6 7 8 9;
test 4 = "$(cat otherfile-4)" &&
git am --abort &&
test_cmp_rev initial HEAD &&
- test -z $(git ls-files -u) &&
+ test -z "$(git ls-files -u)" &&
test_path_is_missing otherfile-4
'
git index-pack test-3.pack &&
cmp test-3.idx test-3-${packname_3}.idx &&
+ cat test-1-${packname_1}.pack >test-4.pack &&
+ rm -f test-4.keep &&
+ git index-pack --keep=why test-4.pack &&
+ cmp test-1-${packname_1}.idx test-4.idx &&
+ test -f test-4.keep &&
+
:'
test_expect_success 'unpacking with --strict' '
test "$victim_orig" = "$victim_head"
'
+test_expect_success 'send-pack --all sends all branches' '
+ # make sure we have at least 2 branches with different
+ # values, just to be thorough
+ git branch other-branch HEAD^ &&
+
+ git init --bare all.git &&
+ git send-pack --all all.git &&
+ git for-each-ref refs/heads >expect &&
+ git -C all.git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'push --all excludes remote-tracking hierarchy' '
mkdir parent &&
(
git fsck
)
'
+
+test_expect_success 'fetch-pack can fetch a raw sha1' '
+ git init hidden &&
+ (
+ cd hidden &&
+ test_commit 1 &&
+ test_commit 2 &&
+ git update-ref refs/hidden/one HEAD^ &&
+ git config transfer.hiderefs refs/hidden &&
+ git config uploadpack.allowtipsha1inwant true
+ ) &&
+ git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one)
+'
+
check_prot_path () {
cat >expected <<-EOF &&
Diag: url=$1
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' '
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
(
cd auto-gc &&
git config gc.autoPackLimit 1 &&
+ git config gc.autoDetach false &&
GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 &&
! grep "Should I try again" fetch.out
)
)
'
-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);
# 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
#IPv6
for tuah in ::1 [::1] [::1]: user@::1 user@[::1] user@[::1]: [user@::1] [user@::1]:
do
- ehost=$(echo $tuah | sed -e "s/1]:/1]/ "| tr -d "[]")
+ ehost=$(echo $tuah | sed -e "s/1]:/1]/" | tr -d "[]")
test_expect_success "clone ssh://$tuah/home/user/repo" "
test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo
"
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=$(pwd)
+
+U=$base_dir/UPLOAD_LOG
+
+# create a commit in repo $1 with name $2
+commit_in () {
+ (
+ cd "$1" &&
+ echo "$2" >"$2" &&
+ git add "$2" &&
+ git commit -m "$2"
+ )
+}
+
+# check that there are $2 loose objects in repo $1
+test_objcount () {
+ echo "$2" >expect &&
+ git -C "$1" count-objects >actual.raw &&
+ cut -d' ' -f1 <actual.raw >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'preparing first repository' '
+ test_create_repo A &&
+ commit_in A file1
+'
+
+test_expect_success 'preparing second repository' '
+ git clone A B &&
+ commit_in B file2 &&
+ git -C B repack -ad &&
+ git -C B prune
+'
+
+test_expect_success 'cloning with reference (-l -s)' '
+ git clone -l -s --reference B A C
+'
+
+test_expect_success 'existence of info/alternates' '
+ test_line_count = 2 C/.git/objects/info/alternates
+'
+
+test_expect_success 'pulling from reference' '
+ git -C C pull ../B master
+'
+
+test_expect_success 'that reference gets used' '
+ test_objcount C 0
+'
+
+test_expect_success 'cloning with reference (no -l -s)' '
+ GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
+'
+
+test_expect_success 'fetched no objects' '
+ test -s "$U.D" &&
+ ! grep " want" "$U.D"
+'
+
+test_expect_success 'existence of info/alternates' '
+ test_line_count = 1 D/.git/objects/info/alternates
+'
+
+test_expect_success 'pulling from reference' '
+ git -C D pull ../B master
+'
+
+test_expect_success 'that reference gets used' '
+ test_objcount D 0
+'
+
+test_expect_success 'updating origin' '
+ commit_in A file3 &&
+ git -C A repack -ad &&
+ git -C A prune
+'
+
+test_expect_success 'pulling changes from origin' '
+ git -C C pull origin
+'
+
+# the 2 local objects are commit and tree from the merge
+test_expect_success 'that alternate to origin gets used' '
+ test_objcount C 2
+'
+
+test_expect_success 'pulling changes from origin' '
+ git -C D pull origin
+'
+
+# the 5 local objects are expected; file3 blob, commit in A to add it
+# and its tree, and 2 are our tree and the merge commit.
+test_expect_success 'check objects expected to exist locally' '
+ test_objcount D 5
+'
+
+test_expect_success 'preparing alternate repository #1' '
+ test_create_repo F &&
+ commit_in F file1
+'
+
+test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
+ git clone F G &&
+ commit_in F file2
+'
+
+test_expect_success 'cloning alternate repo #1, using #2 as reference' '
+ git clone --reference G F H
+'
+
+test_expect_success 'cloning with reference being subset of source (-l -s)' '
+ git clone -l -s --reference A B E
+'
+
+test_expect_success 'cloning with multiple references drops duplicates' '
+ git clone -s --reference B --reference A --reference B A dups &&
+ test_line_count = 2 dups/.git/objects/info/alternates
+'
+
+test_expect_success 'clone with reference from a tagged repository' '
+ (
+ cd A && git tag -a -m tagged HEAD
+ ) &&
+ git clone --reference=A A I
+'
+
+test_expect_success 'prepare branched repository' '
+ git clone A J &&
+ (
+ cd J &&
+ git checkout -b other master^ &&
+ echo other >otherfile &&
+ git add otherfile &&
+ git commit -m other &&
+ git checkout master
+ )
+'
+
+test_expect_success 'fetch with incomplete alternates' '
+ git init K &&
+ echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
+ (
+ cd K &&
+ git remote add J "file://$base_dir/J" &&
+ GIT_TRACE_PACKET=$U.K git fetch J
+ ) &&
+ master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
+ test -s "$U.K" &&
+ ! grep " want $master_object" "$U.K" &&
+ tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
+ ! grep " want $tag_object" "$U.K"
+'
+
+test_expect_success 'clone using repo with gitfile as a reference' '
+ git clone --separate-git-dir=L A M &&
+ git clone --reference=M A N &&
+ echo "$base_dir/L/objects" >expected &&
+ test_cmp expected "$base_dir/N/.git/objects/info/alternates"
+'
+
+test_expect_success 'clone using repo pointed at by gitfile as reference' '
+ git clone --reference=M/.git A O &&
+ echo "$base_dir/L/objects" >expected &&
+ test_cmp expected "$base_dir/O/.git/objects/info/alternates"
+'
+
+test_expect_success 'clone and dissociate from reference' '
+ git init P &&
+ (
+ cd P && test_commit one
+ ) &&
+ git clone P Q &&
+ (
+ cd Q && test_commit two
+ ) &&
+ git clone --no-local --reference=P Q R &&
+ git clone --no-local --reference=P --dissociate Q S &&
+ # removing the reference P would corrupt R but not S
+ rm -fr P &&
+ test_must_fail git -C R fsck &&
+ git -C S fsck
+'
+test_expect_success 'clone, dissociate from partial reference and repack' '
+ rm -fr P Q R &&
+ git init P &&
+ (
+ cd P &&
+ test_commit one &&
+ git repack &&
+ test_commit two &&
+ git repack
+ ) &&
+ git clone --bare P Q &&
+ (
+ cd P &&
+ git checkout -b second &&
+ test_commit three &&
+ git repack
+ ) &&
+ git clone --bare --dissociate --reference=P Q R &&
+ ls R/objects/pack/*.pack >packs.txt &&
+ test_line_count = 1 packs.txt
+'
+
+test_expect_success 'clone, dissociate from alternates' '
+ rm -fr A B C &&
+ test_create_repo A &&
+ commit_in A file1 &&
+ git clone --reference=A A B &&
+ test_line_count = 1 B/.git/objects/info/alternates &&
+ git clone --local --dissociate B C &&
+ ! test -f C/.git/objects/info/alternates &&
+ ( cd C && git fsck )
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test local clone'
+. ./test-lib.sh
+
+repo_is_hardlinked() {
+ find "$1/objects" -type f -links 1 >output &&
+ test_line_count = 0 output
+}
+
+test_expect_success 'preparing origin repository' '
+ : >file && git add . && git commit -m1 &&
+ git clone --bare . a.git &&
+ git clone --bare . x &&
+ test "$(cd a.git && git config --bool core.bare)" = true &&
+ test "$(cd x && git config --bool core.bare)" = true &&
+ git bundle create b1.bundle --all &&
+ git bundle create b2.bundle master &&
+ mkdir dir &&
+ cp b1.bundle dir/b3 &&
+ cp b1.bundle b4
+'
+
+test_expect_success 'local clone without .git suffix' '
+ git clone -l -s a b &&
+ (cd b &&
+ test "$(git config --bool core.bare)" = false &&
+ git fetch)
+'
+
+test_expect_success 'local clone with .git suffix' '
+ git clone -l -s a.git c &&
+ (cd c && git fetch)
+'
+
+test_expect_success 'local clone from x' '
+ git clone -l -s x y &&
+ (cd y && git fetch)
+'
+
+test_expect_success 'local clone from x.git that does not exist' '
+ test_must_fail git clone -l -s x.git z
+'
+
+test_expect_success 'With -no-hardlinks, local will make a copy' '
+ git clone --bare --no-hardlinks x w &&
+ ! repo_is_hardlinked w
+'
+
+test_expect_success 'Even without -l, local will make a hardlink' '
+ rm -fr w &&
+ git clone -l --bare x w &&
+ repo_is_hardlinked w
+'
+
+test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
+ echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
+ git clone a d &&
+ (cd d &&
+ git fetch &&
+ test ! -e .git/refs/remotes/origin/HEAD)
+'
+
+test_expect_success 'bundle clone without .bundle suffix' '
+ git clone dir/b3 &&
+ (cd b3 && git fetch)
+'
+
+test_expect_success 'bundle clone with .bundle suffix' '
+ git clone b1.bundle &&
+ (cd b1 && git fetch)
+'
+
+test_expect_success 'bundle clone from b4' '
+ git clone b4 bdl &&
+ (cd bdl && git fetch)
+'
+
+test_expect_success 'bundle clone from b4.bundle that does not exist' '
+ test_must_fail git clone b4.bundle bb
+'
+
+test_expect_success 'bundle clone with nonexistent HEAD' '
+ git clone b2.bundle b2 &&
+ (cd b2 &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/heads/master)
+'
+
+test_expect_success 'clone empty repository' '
+ mkdir empty &&
+ (cd empty &&
+ git init &&
+ git config receive.denyCurrentBranch warn) &&
+ git clone empty empty-clone &&
+ test_tick &&
+ (cd empty-clone
+ echo "content" >> foo &&
+ git add foo &&
+ git commit -m "Initial commit" &&
+ git push origin master &&
+ expected=$(git rev-parse master) &&
+ actual=$(git --git-dir=../empty/.git rev-parse master) &&
+ test $actual = $expected)
+'
+
+test_expect_success 'clone empty repository, and then push should not segfault.' '
+ rm -fr empty/ empty-clone/ &&
+ mkdir empty &&
+ (cd empty && git init) &&
+ git clone empty empty-clone &&
+ (cd empty-clone &&
+ test_must_fail git push)
+'
+
+test_expect_success 'cloning non-existent directory fails' '
+ rm -rf does-not-exist &&
+ test_must_fail git clone does-not-exist
+'
+
+test_expect_success 'cloning non-git directory fails' '
+ rm -rf not-a-git-repo not-a-git-repo-clone &&
+ mkdir not-a-git-repo &&
+ test_must_fail git clone not-a-git-repo not-a-git-repo-clone
+'
+
+test_expect_success 'cloning file:// does not hardlink' '
+ git clone --bare file://"$(pwd)"/a non-local &&
+ ! repo_is_hardlinked non-local
+'
+
+test_expect_success 'cloning a local path with --no-local does not hardlink' '
+ git clone --bare --no-local a force-nonlocal &&
+ ! repo_is_hardlinked force-nonlocal
+'
+
+test_expect_success 'cloning locally respects "-u" for fetching refs' '
+ test_must_fail git clone --bare -u false a should_not_work.git
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='basic clone options'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >file && git add file &&
+ git commit -m one)
+
+'
+
+test_expect_success 'clone -o' '
+
+ git clone -o foo parent clone-o &&
+ (cd clone-o && git rev-parse --verify refs/remotes/foo/master)
+
+'
+
+test_expect_success 'redirected clone does not show progress' '
+
+ git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
+ ! grep % err &&
+ test_i18ngrep ! "Checking connectivity" err
+
+'
+
+test_expect_success 'redirected clone -v does show progress' '
+
+ git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
+ >out 2>err &&
+ grep % err
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='some bundle related tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit initial &&
+ test_tick &&
+ git tag -m tag tag &&
+ test_commit second &&
+ test_commit third &&
+ git tag -d initial &&
+ git tag -d second &&
+ git tag -d third
+'
+
+test_expect_success 'annotated tags can be excluded by rev-list options' '
+ git bundle create bundle --all --since=7.Apr.2005.15:14:00.-0700 &&
+ git ls-remote bundle > output &&
+ grep tag output &&
+ git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
+ git ls-remote bundle > output &&
+ ! grep tag output
+'
+
+test_expect_success 'die if bundle file cannot be created' '
+ mkdir adir &&
+ test_must_fail git bundle create adir --all
+'
+
+test_expect_failure 'bundle --stdin' '
+ echo master | git bundle create stdin-bundle.bdl --stdin &&
+ git ls-remote stdin-bundle.bdl >output &&
+ grep master output
+'
+
+test_expect_failure 'bundle --stdin <rev-list options>' '
+ echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
+ git ls-remote hybrid-bundle.bdl >output &&
+ grep master output
+'
+
+test_expect_success 'empty bundle file is rejected' '
+ : >empty-bundle &&
+ test_must_fail git fetch empty-bundle
+'
+
+# This triggers a bug in older versions where the resulting line (with
+# --pretty=oneline) was longer than a 1024-char buffer.
+test_expect_success 'ridiculously long subject in boundary' '
+ : >file4 &&
+ test_tick &&
+ git add file4 &&
+ printf "%01200d\n" 0 | git commit -F - &&
+ test_commit fifth &&
+ git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
+ git bundle list-heads long-subject-bundle.bdl >heads &&
+ test -s heads &&
+ git fetch long-subject-bundle.bdl &&
+ sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
+ grep "^-[0-9a-f]\\{40\\} " boundary
+'
+
+test_expect_success 'prerequisites with an empty commit message' '
+ : >file1 &&
+ git add file1 &&
+ test_tick &&
+ git commit --allow-empty-message -m "" &&
+ test_commit file2 &&
+ git bundle create bundle HEAD^.. &&
+ git bundle verify bundle
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='Test cloning a repository larger than 2 gigabyte'
+. ./test-lib.sh
+
+if test -z "$GIT_TEST_CLONE_2GB"
+then
+ say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
+else
+ test_set_prereq CLONE_2GB
+fi
+
+test_expect_success CLONE_2GB 'setup' '
+
+ git config pack.compression 0 &&
+ git config pack.depth 0 &&
+ blobsize=$((100*1024*1024)) &&
+ blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
+ i=1 &&
+ (while test $i -le $blobcount
+ do
+ printf "Generating blob $i/$blobcount\r" >&2 &&
+ printf "blob\nmark :$i\ndata $blobsize\n" &&
+ #test-genrandom $i $blobsize &&
+ printf "%-${blobsize}s" $i &&
+ echo "M 100644 :$i $i" >> commit
+ i=$(($i+1)) ||
+ echo $? > exit-status
+ done &&
+ echo "commit refs/heads/master" &&
+ echo "author A U Thor <author@email.com> 123456789 +0000" &&
+ echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+ echo "data 5" &&
+ echo ">2gb" &&
+ cat commit) |
+ git fast-import --big-file-threshold=2 &&
+ test ! -f exit-status
+
+'
+
+test_expect_success CLONE_2GB 'clone - bare' '
+
+ git clone --bare --no-hardlinks . clone-bare
+
+'
+
+test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
+
+ git clone "file://$(pwd)" clone-wt
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='clone --branch option'
+. ./test-lib.sh
+
+check_HEAD() {
+ echo refs/heads/"$1" >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual
+}
+
+check_file() {
+ echo "$1" >expect &&
+ test_cmp expect file
+}
+
+test_expect_success 'setup' '
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >file && git add file && git commit -m one &&
+ git checkout -b two &&
+ echo two >file && git add file && git commit -m two &&
+ git checkout master) &&
+ mkdir empty &&
+ (cd empty && git init)
+'
+
+test_expect_success 'vanilla clone chooses HEAD' '
+ git clone parent clone &&
+ (cd clone &&
+ check_HEAD master &&
+ check_file one
+ )
+'
+
+test_expect_success 'clone -b chooses specified branch' '
+ git clone -b two parent clone-two &&
+ (cd clone-two &&
+ check_HEAD two &&
+ check_file two
+ )
+'
+
+test_expect_success 'clone -b sets up tracking' '
+ (cd clone-two &&
+ echo origin >expect &&
+ git config branch.two.remote >actual &&
+ echo refs/heads/two >>expect &&
+ git config branch.two.merge >>actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
+ (cd clone-two &&
+ echo refs/remotes/origin/master >expect &&
+ git symbolic-ref refs/remotes/origin/HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'clone -b with bogus branch' '
+ test_must_fail git clone -b bogus parent clone-bogus
+'
+
+test_expect_success 'clone -b not allowed with empty repos' '
+ test_must_fail git clone -b branch empty clone-branch-empty
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test cloning a repository with detached HEAD'
+. ./test-lib.sh
+
+head_is_detached() {
+ git --git-dir=$1/.git rev-parse --verify HEAD &&
+ test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
+}
+
+test_expect_success 'setup' '
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two &&
+ git tag two &&
+ echo three >file &&
+ git commit -a -m three
+'
+
+test_expect_success 'clone repo (detached HEAD points to branch)' '
+ git checkout master^0 &&
+ git clone "file://$PWD" detached-branch
+'
+test_expect_success 'cloned HEAD matches' '
+ echo three >expect &&
+ git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_failure 'cloned HEAD is detached' '
+ head_is_detached detached-branch
+'
+
+test_expect_success 'clone repo (detached HEAD points to tag)' '
+ git checkout two^0 &&
+ git clone "file://$PWD" detached-tag
+'
+test_expect_success 'cloned HEAD matches' '
+ echo two >expect &&
+ git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-tag
+'
+
+test_expect_success 'clone repo (detached HEAD points to history)' '
+ git checkout two^ &&
+ git clone "file://$PWD" detached-history
+'
+test_expect_success 'cloned HEAD matches' '
+ echo one >expect &&
+ git --git-dir=detached-history/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-history
+'
+
+test_expect_success 'clone repo (orphan detached HEAD)' '
+ git checkout master^0 &&
+ echo four >file &&
+ git commit -a -m four &&
+ git clone "file://$PWD" detached-orphan
+'
+test_expect_success 'cloned HEAD matches' '
+ echo four >expect &&
+ git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-orphan
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='tests for git clone -c key=value'
+. ./test-lib.sh
+
+test_expect_success 'clone -c sets config in cloned repo' '
+ rm -rf child &&
+ git clone -c core.foo=bar . child &&
+ echo bar >expect &&
+ git --git-dir=child/.git config core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c can set multi-keys' '
+ rm -rf child &&
+ git clone -c core.foo=bar -c core.foo=baz . child &&
+ { echo bar; echo baz; } >expect &&
+ git --git-dir=child/.git config --get-all core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c without a value is boolean true' '
+ rm -rf child &&
+ git clone -c core.foo . child &&
+ echo true >expect &&
+ git --git-dir=child/.git config --bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c config is available during clone' '
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+ rm -rf child &&
+ git clone -c core.autocrlf . child &&
+ printf "content\\r\\n" >expect &&
+ test_cmp expect child/file
+'
+
+# Tests for the hidden file attribute on windows
+is_hidden () {
+ # Use the output of `attrib`, ignore the absolute path
+ case "$(attrib "$1")" in *H*?:*) return 0;; esac
+ return 1
+}
+
+test_expect_success MINGW 'clone -c core.hideDotFiles' '
+ test_commit attributes .gitattributes "" &&
+ rm -rf child &&
+ git clone -c core.hideDotFiles=false . child &&
+ ! is_hidden child/.gitattributes &&
+ rm -rf child &&
+ git clone -c core.hideDotFiles=dotGitOnly . child &&
+ ! is_hidden child/.gitattributes &&
+ rm -rf child &&
+ git clone -c core.hideDotFiles=true . child &&
+ is_hidden child/.gitattributes
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test refspec written by clone-command'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ # Make two branches, "master" and "side"
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two &&
+ git tag two &&
+ echo three >file &&
+ git commit -a -m three &&
+ git checkout -b side &&
+ echo four >file &&
+ git commit -a -m four &&
+ git checkout master &&
+
+ # default clone
+ git clone . dir_all &&
+
+ # default --single that follows HEAD=master
+ git clone --single-branch . dir_master &&
+
+ # default --single that follows HEAD=side
+ git checkout side &&
+ git clone --single-branch . dir_side &&
+
+ # explicit --single that follows side
+ git checkout master &&
+ git clone --single-branch --branch side . dir_side2 &&
+
+ # default --single with --mirror
+ git clone --single-branch --mirror . dir_mirror &&
+
+ # default --single with --branch and --mirror
+ git clone --single-branch --mirror --branch side . dir_mirror_side &&
+
+ # --single that does not know what branch to follow
+ git checkout two^ &&
+ git clone --single-branch . dir_detached &&
+
+ # explicit --single with tag
+ git clone --single-branch --branch two . dir_tag &&
+
+ # advance both "master" and "side" branches
+ git checkout side &&
+ echo five >file &&
+ git commit -a -m five &&
+ git checkout master &&
+ echo six >file &&
+ git commit -a -m six &&
+
+ # update tag
+ git tag -d two && git tag two
+'
+
+test_expect_success 'by default all branches will be kept updated' '
+ (
+ cd dir_all && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # follow both master and side
+ git for-each-ref refs/heads >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'by default no tags will be kept updated' '
+ (
+ cd dir_all && git fetch &&
+ git for-each-ref refs/tags >../actual
+ ) &&
+ git for-each-ref refs/tags >expect &&
+ test_must_fail test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at master' '
+ (
+ cd dir_master && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow master
+ git for-each-ref refs/heads/master >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at side' '
+ (
+ cd dir_side && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow side
+ git for-each-ref refs/heads/side >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch side' '
+ (
+ cd dir_side2 && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow side
+ git for-each-ref refs/heads/side >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
+ (
+ cd dir_tag && git fetch &&
+ git for-each-ref refs/tags >../actual
+ ) &&
+ git for-each-ref refs/tags >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with --mirror' '
+ (
+ cd dir_mirror && git fetch &&
+ git for-each-ref refs > ../actual
+ ) &&
+ git for-each-ref refs >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch and --mirror' '
+ (
+ cd dir_mirror_side && git fetch &&
+ git for-each-ref refs > ../actual
+ ) &&
+ git for-each-ref refs >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with detached' '
+ (
+ cd dir_detached && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # nothing
+ >expect &&
+ test_cmp expect actual
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test transitive info/alternate entries'
+. ./test-lib.sh
+
+# test that a file is not reachable in the current repository
+# but that it is after creating a info/alternate entry
+reachable_via() {
+ alternate="$1"
+ file="$2"
+ if git cat-file -e "HEAD:$file"; then return 1; fi
+ echo "$alternate" >> .git/objects/info/alternate
+ git cat-file -e "HEAD:$file"
+}
+
+test_valid_repo() {
+ git fsck --full > fsck.log &&
+ test_line_count = 0 fsck.log
+}
+
+base_dir=$(pwd)
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo "Hello World" > file1 &&
+git add file1 &&
+git commit -m "Initial commit" file1 &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone -l -s A B && cd B &&
+echo "foo bar" > file2 &&
+git add file2 &&
+git commit -m "next commit" file2 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing third repository' \
+'git clone -l -s B C && cd C &&
+echo "Goodbye, cruel world" > file3 &&
+git add file3 &&
+git commit -m "one more" file3 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'creating too deep nesting' \
+'git clone -l -s C D &&
+git clone -l -s D E &&
+git clone -l -s E F &&
+git clone -l -s F G &&
+git clone --bare -l -s G H'
+
+test_expect_success 'invalidity of deepest repository' \
+'cd H && {
+ test_valid_repo
+ test $? -ne 0
+}'
+
+cd "$base_dir"
+
+test_expect_success 'validity of third repository' \
+'cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of fourth repository' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'breaking of loops' \
+'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&&
+cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'that info/alternates is necessary' \
+'cd C &&
+rm -f .git/objects/info/alternates &&
+! (test_valid_repo)'
+
+cd "$base_dir"
+
+test_expect_success 'that relative alternate is possible for current dir' \
+'cd C &&
+echo "../../../B/.git/objects" > .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success \
+ 'that relative alternate is only possible for current dir' '
+ cd D &&
+ ! (test_valid_repo)
+'
+
+cd "$base_dir"
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
-#
-
-test_description='test clone --reference'
-. ./test-lib.sh
-
-base_dir=$(pwd)
-
-U=$base_dir/UPLOAD_LOG
-
-# create a commit in repo $1 with name $2
-commit_in () {
- (
- cd "$1" &&
- echo "$2" >"$2" &&
- git add "$2" &&
- git commit -m "$2"
- )
-}
-
-# check that there are $2 loose objects in repo $1
-test_objcount () {
- echo "$2" >expect &&
- git -C "$1" count-objects >actual.raw &&
- cut -d' ' -f1 <actual.raw >actual &&
- test_cmp expect actual
-}
-
-test_expect_success 'preparing first repository' '
- test_create_repo A &&
- commit_in A file1
-'
-
-test_expect_success 'preparing second repository' '
- git clone A B &&
- commit_in B file2 &&
- git -C B repack -ad &&
- git -C B prune
-'
-
-test_expect_success 'cloning with reference (-l -s)' '
- git clone -l -s --reference B A C
-'
-
-test_expect_success 'existence of info/alternates' '
- test_line_count = 2 C/.git/objects/info/alternates
-'
-
-test_expect_success 'pulling from reference' '
- git -C C pull ../B master
-'
-
-test_expect_success 'that reference gets used' '
- test_objcount C 0
-'
-
-test_expect_success 'cloning with reference (no -l -s)' '
- GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
-'
-
-test_expect_success 'fetched no objects' '
- test -s "$U.D" &&
- ! grep " want" "$U.D"
-'
-
-test_expect_success 'existence of info/alternates' '
- test_line_count = 1 D/.git/objects/info/alternates
-'
-
-test_expect_success 'pulling from reference' '
- git -C D pull ../B master
-'
-
-test_expect_success 'that reference gets used' '
- test_objcount D 0
-'
-
-test_expect_success 'updating origin' '
- commit_in A file3 &&
- git -C A repack -ad &&
- git -C A prune
-'
-
-test_expect_success 'pulling changes from origin' '
- git -C C pull origin
-'
-
-# the 2 local objects are commit and tree from the merge
-test_expect_success 'that alternate to origin gets used' '
- test_objcount C 2
-'
-
-test_expect_success 'pulling changes from origin' '
- git -C D pull origin
-'
-
-# the 5 local objects are expected; file3 blob, commit in A to add it
-# and its tree, and 2 are our tree and the merge commit.
-test_expect_success 'check objects expected to exist locally' '
- test_objcount D 5
-'
-
-test_expect_success 'preparing alternate repository #1' '
- test_create_repo F &&
- commit_in F file1
-'
-
-test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
- git clone F G &&
- commit_in F file2
-'
-
-test_expect_success 'cloning alternate repo #1, using #2 as reference' '
- git clone --reference G F H
-'
-
-test_expect_success 'cloning with reference being subset of source (-l -s)' '
- git clone -l -s --reference A B E
-'
-
-test_expect_success 'cloning with multiple references drops duplicates' '
- git clone -s --reference B --reference A --reference B A dups &&
- test_line_count = 2 dups/.git/objects/info/alternates
-'
-
-test_expect_success 'clone with reference from a tagged repository' '
- (
- cd A && git tag -a -m tagged HEAD
- ) &&
- git clone --reference=A A I
-'
-
-test_expect_success 'prepare branched repository' '
- git clone A J &&
- (
- cd J &&
- git checkout -b other master^ &&
- echo other >otherfile &&
- git add otherfile &&
- git commit -m other &&
- git checkout master
- )
-'
-
-test_expect_success 'fetch with incomplete alternates' '
- git init K &&
- echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
- (
- cd K &&
- git remote add J "file://$base_dir/J" &&
- GIT_TRACE_PACKET=$U.K git fetch J
- ) &&
- master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
- test -s "$U.K" &&
- ! grep " want $master_object" "$U.K" &&
- tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
- ! grep " want $tag_object" "$U.K"
-'
-
-test_expect_success 'clone using repo with gitfile as a reference' '
- git clone --separate-git-dir=L A M &&
- git clone --reference=M A N &&
- echo "$base_dir/L/objects" >expected &&
- test_cmp expected "$base_dir/N/.git/objects/info/alternates"
-'
-
-test_expect_success 'clone using repo pointed at by gitfile as reference' '
- git clone --reference=M/.git A O &&
- echo "$base_dir/L/objects" >expected &&
- test_cmp expected "$base_dir/O/.git/objects/info/alternates"
-'
-
-test_expect_success 'clone and dissociate from reference' '
- git init P &&
- (
- cd P && test_commit one
- ) &&
- git clone P Q &&
- (
- cd Q && test_commit two
- ) &&
- git clone --no-local --reference=P Q R &&
- git clone --no-local --reference=P --dissociate Q S &&
- # removing the reference P would corrupt R but not S
- rm -fr P &&
- test_must_fail git -C R fsck &&
- git -C S fsck
-'
-test_expect_success 'clone, dissociate from partial reference and repack' '
- rm -fr P Q R &&
- git init P &&
- (
- cd P &&
- test_commit one &&
- git repack &&
- test_commit two &&
- git repack
- ) &&
- git clone --bare P Q &&
- (
- cd P &&
- git checkout -b second &&
- test_commit three &&
- git repack
- ) &&
- git clone --bare --dissociate --reference=P Q R &&
- ls R/objects/pack/*.pack >packs.txt &&
- test_line_count = 1 packs.txt
-'
-
-test_expect_success 'clone, dissociate from alternates' '
- rm -fr A B C &&
- test_create_repo A &&
- commit_in A file1 &&
- git clone --reference=A A B &&
- test_line_count = 1 B/.git/objects/info/alternates &&
- git clone --local --dissociate B C &&
- ! test -f C/.git/objects/info/alternates &&
- ( cd C && git fsck )
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test local clone'
-. ./test-lib.sh
-
-repo_is_hardlinked() {
- find "$1/objects" -type f -links 1 >output &&
- test_line_count = 0 output
-}
-
-test_expect_success 'preparing origin repository' '
- : >file && git add . && git commit -m1 &&
- git clone --bare . a.git &&
- git clone --bare . x &&
- test "$(cd a.git && git config --bool core.bare)" = true &&
- test "$(cd x && git config --bool core.bare)" = true &&
- git bundle create b1.bundle --all &&
- git bundle create b2.bundle master &&
- mkdir dir &&
- cp b1.bundle dir/b3 &&
- cp b1.bundle b4
-'
-
-test_expect_success 'local clone without .git suffix' '
- git clone -l -s a b &&
- (cd b &&
- test "$(git config --bool core.bare)" = false &&
- git fetch)
-'
-
-test_expect_success 'local clone with .git suffix' '
- git clone -l -s a.git c &&
- (cd c && git fetch)
-'
-
-test_expect_success 'local clone from x' '
- git clone -l -s x y &&
- (cd y && git fetch)
-'
-
-test_expect_success 'local clone from x.git that does not exist' '
- test_must_fail git clone -l -s x.git z
-'
-
-test_expect_success 'With -no-hardlinks, local will make a copy' '
- git clone --bare --no-hardlinks x w &&
- ! repo_is_hardlinked w
-'
-
-test_expect_success 'Even without -l, local will make a hardlink' '
- rm -fr w &&
- git clone -l --bare x w &&
- repo_is_hardlinked w
-'
-
-test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
- echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
- git clone a d &&
- (cd d &&
- git fetch &&
- test ! -e .git/refs/remotes/origin/HEAD)
-'
-
-test_expect_success 'bundle clone without .bundle suffix' '
- git clone dir/b3 &&
- (cd b3 && git fetch)
-'
-
-test_expect_success 'bundle clone with .bundle suffix' '
- git clone b1.bundle &&
- (cd b1 && git fetch)
-'
-
-test_expect_success 'bundle clone from b4' '
- git clone b4 bdl &&
- (cd bdl && git fetch)
-'
-
-test_expect_success 'bundle clone from b4.bundle that does not exist' '
- test_must_fail git clone b4.bundle bb
-'
-
-test_expect_success 'bundle clone with nonexistent HEAD' '
- git clone b2.bundle b2 &&
- (cd b2 &&
- git fetch &&
- test_must_fail git rev-parse --verify refs/heads/master)
-'
-
-test_expect_success 'clone empty repository' '
- mkdir empty &&
- (cd empty &&
- git init &&
- git config receive.denyCurrentBranch warn) &&
- git clone empty empty-clone &&
- test_tick &&
- (cd empty-clone
- echo "content" >> foo &&
- git add foo &&
- git commit -m "Initial commit" &&
- git push origin master &&
- expected=$(git rev-parse master) &&
- actual=$(git --git-dir=../empty/.git rev-parse master) &&
- test $actual = $expected)
-'
-
-test_expect_success 'clone empty repository, and then push should not segfault.' '
- rm -fr empty/ empty-clone/ &&
- mkdir empty &&
- (cd empty && git init) &&
- git clone empty empty-clone &&
- (cd empty-clone &&
- test_must_fail git push)
-'
-
-test_expect_success 'cloning non-existent directory fails' '
- rm -rf does-not-exist &&
- test_must_fail git clone does-not-exist
-'
-
-test_expect_success 'cloning non-git directory fails' '
- rm -rf not-a-git-repo not-a-git-repo-clone &&
- mkdir not-a-git-repo &&
- test_must_fail git clone not-a-git-repo not-a-git-repo-clone
-'
-
-test_expect_success 'cloning file:// does not hardlink' '
- git clone --bare file://"$(pwd)"/a non-local &&
- ! repo_is_hardlinked non-local
-'
-
-test_expect_success 'cloning a local path with --no-local does not hardlink' '
- git clone --bare --no-local a force-nonlocal &&
- ! repo_is_hardlinked force-nonlocal
-'
-
-test_expect_success 'cloning locally respects "-u" for fetching refs' '
- test_must_fail git clone --bare -u false a should_not_work.git
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='basic clone options'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-
- mkdir parent &&
- (cd parent && git init &&
- echo one >file && git add file &&
- git commit -m one)
-
-'
-
-test_expect_success 'clone -o' '
-
- git clone -o foo parent clone-o &&
- (cd clone-o && git rev-parse --verify refs/remotes/foo/master)
-
-'
-
-test_expect_success 'redirected clone does not show progress' '
-
- git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
- ! grep % err &&
- test_i18ngrep ! "Checking connectivity" err
-
-'
-
-test_expect_success 'redirected clone -v does show progress' '
-
- git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
- >out 2>err &&
- grep % err
-
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='some bundle related tests'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
- test_commit initial &&
- test_tick &&
- git tag -m tag tag &&
- test_commit second &&
- test_commit third &&
- git tag -d initial &&
- git tag -d second &&
- git tag -d third
-'
-
-test_expect_success 'annotated tags can be excluded by rev-list options' '
- git bundle create bundle --all --since=7.Apr.2005.15:14:00.-0700 &&
- git ls-remote bundle > output &&
- grep tag output &&
- git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
- git ls-remote bundle > output &&
- ! grep tag output
-'
-
-test_expect_success 'die if bundle file cannot be created' '
- mkdir adir &&
- test_must_fail git bundle create adir --all
-'
-
-test_expect_failure 'bundle --stdin' '
- echo master | git bundle create stdin-bundle.bdl --stdin &&
- git ls-remote stdin-bundle.bdl >output &&
- grep master output
-'
-
-test_expect_failure 'bundle --stdin <rev-list options>' '
- echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
- git ls-remote hybrid-bundle.bdl >output &&
- grep master output
-'
-
-test_expect_success 'empty bundle file is rejected' '
- : >empty-bundle &&
- test_must_fail git fetch empty-bundle
-'
-
-# This triggers a bug in older versions where the resulting line (with
-# --pretty=oneline) was longer than a 1024-char buffer.
-test_expect_success 'ridiculously long subject in boundary' '
- : >file4 &&
- test_tick &&
- git add file4 &&
- printf "%01200d\n" 0 | git commit -F - &&
- test_commit fifth &&
- git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
- git bundle list-heads long-subject-bundle.bdl >heads &&
- test -s heads &&
- git fetch long-subject-bundle.bdl &&
- sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
- grep "^-[0-9a-f]\\{40\\} " boundary
-'
-
-test_expect_success 'prerequisites with an empty commit message' '
- : >file1 &&
- git add file1 &&
- test_tick &&
- git commit --allow-empty-message -m "" &&
- test_commit file2 &&
- git bundle create bundle HEAD^.. &&
- git bundle verify bundle
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='Test cloning a repository larger than 2 gigabyte'
-. ./test-lib.sh
-
-if test -z "$GIT_TEST_CLONE_2GB"
-then
- say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
-else
- test_set_prereq CLONE_2GB
-fi
-
-test_expect_success CLONE_2GB 'setup' '
-
- git config pack.compression 0 &&
- git config pack.depth 0 &&
- blobsize=$((100*1024*1024)) &&
- blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
- i=1 &&
- (while test $i -le $blobcount
- do
- printf "Generating blob $i/$blobcount\r" >&2 &&
- printf "blob\nmark :$i\ndata $blobsize\n" &&
- #test-genrandom $i $blobsize &&
- printf "%-${blobsize}s" $i &&
- echo "M 100644 :$i $i" >> commit
- i=$(($i+1)) ||
- echo $? > exit-status
- done &&
- echo "commit refs/heads/master" &&
- echo "author A U Thor <author@email.com> 123456789 +0000" &&
- echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
- echo "data 5" &&
- echo ">2gb" &&
- cat commit) |
- git fast-import --big-file-threshold=2 &&
- test ! -f exit-status
-
-'
-
-test_expect_success CLONE_2GB 'clone - bare' '
-
- git clone --bare --no-hardlinks . clone-bare
-
-'
-
-test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
-
- git clone "file://$(pwd)" clone-wt
-
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='clone --branch option'
-. ./test-lib.sh
-
-check_HEAD() {
- echo refs/heads/"$1" >expect &&
- git symbolic-ref HEAD >actual &&
- test_cmp expect actual
-}
-
-check_file() {
- echo "$1" >expect &&
- test_cmp expect file
-}
-
-test_expect_success 'setup' '
- mkdir parent &&
- (cd parent && git init &&
- echo one >file && git add file && git commit -m one &&
- git checkout -b two &&
- echo two >file && git add file && git commit -m two &&
- git checkout master) &&
- mkdir empty &&
- (cd empty && git init)
-'
-
-test_expect_success 'vanilla clone chooses HEAD' '
- git clone parent clone &&
- (cd clone &&
- check_HEAD master &&
- check_file one
- )
-'
-
-test_expect_success 'clone -b chooses specified branch' '
- git clone -b two parent clone-two &&
- (cd clone-two &&
- check_HEAD two &&
- check_file two
- )
-'
-
-test_expect_success 'clone -b sets up tracking' '
- (cd clone-two &&
- echo origin >expect &&
- git config branch.two.remote >actual &&
- echo refs/heads/two >>expect &&
- git config branch.two.merge >>actual &&
- test_cmp expect actual
- )
-'
-
-test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
- (cd clone-two &&
- echo refs/remotes/origin/master >expect &&
- git symbolic-ref refs/remotes/origin/HEAD >actual &&
- test_cmp expect actual
- )
-'
-
-test_expect_success 'clone -b with bogus branch' '
- test_must_fail git clone -b bogus parent clone-bogus
-'
-
-test_expect_success 'clone -b not allowed with empty repos' '
- test_must_fail git clone -b branch empty clone-branch-empty
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test cloning a repository with detached HEAD'
-. ./test-lib.sh
-
-head_is_detached() {
- git --git-dir=$1/.git rev-parse --verify HEAD &&
- test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
-}
-
-test_expect_success 'setup' '
- echo one >file &&
- git add file &&
- git commit -m one &&
- echo two >file &&
- git commit -a -m two &&
- git tag two &&
- echo three >file &&
- git commit -a -m three
-'
-
-test_expect_success 'clone repo (detached HEAD points to branch)' '
- git checkout master^0 &&
- git clone "file://$PWD" detached-branch
-'
-test_expect_success 'cloned HEAD matches' '
- echo three >expect &&
- git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_failure 'cloned HEAD is detached' '
- head_is_detached detached-branch
-'
-
-test_expect_success 'clone repo (detached HEAD points to tag)' '
- git checkout two^0 &&
- git clone "file://$PWD" detached-tag
-'
-test_expect_success 'cloned HEAD matches' '
- echo two >expect &&
- git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-tag
-'
-
-test_expect_success 'clone repo (detached HEAD points to history)' '
- git checkout two^ &&
- git clone "file://$PWD" detached-history
-'
-test_expect_success 'cloned HEAD matches' '
- echo one >expect &&
- git --git-dir=detached-history/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-history
-'
-
-test_expect_success 'clone repo (orphan detached HEAD)' '
- git checkout master^0 &&
- echo four >file &&
- git commit -a -m four &&
- git clone "file://$PWD" detached-orphan
-'
-test_expect_success 'cloned HEAD matches' '
- echo four >expect &&
- git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-orphan
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='tests for git clone -c key=value'
-. ./test-lib.sh
-
-test_expect_success 'clone -c sets config in cloned repo' '
- rm -rf child &&
- git clone -c core.foo=bar . child &&
- echo bar >expect &&
- git --git-dir=child/.git config core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c can set multi-keys' '
- rm -rf child &&
- git clone -c core.foo=bar -c core.foo=baz . child &&
- { echo bar; echo baz; } >expect &&
- git --git-dir=child/.git config --get-all core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c without a value is boolean true' '
- rm -rf child &&
- git clone -c core.foo . child &&
- echo true >expect &&
- git --git-dir=child/.git config --bool core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c config is available during clone' '
- echo content >file &&
- git add file &&
- git commit -m one &&
- rm -rf child &&
- git clone -c core.autocrlf . child &&
- printf "content\\r\\n" >expect &&
- test_cmp expect child/file
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test refspec written by clone-command'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
- # Make two branches, "master" and "side"
- echo one >file &&
- git add file &&
- git commit -m one &&
- echo two >file &&
- git commit -a -m two &&
- git tag two &&
- echo three >file &&
- git commit -a -m three &&
- git checkout -b side &&
- echo four >file &&
- git commit -a -m four &&
- git checkout master &&
-
- # default clone
- git clone . dir_all &&
-
- # default --single that follows HEAD=master
- git clone --single-branch . dir_master &&
-
- # default --single that follows HEAD=side
- git checkout side &&
- git clone --single-branch . dir_side &&
-
- # explicit --single that follows side
- git checkout master &&
- git clone --single-branch --branch side . dir_side2 &&
-
- # default --single with --mirror
- git clone --single-branch --mirror . dir_mirror &&
-
- # default --single with --branch and --mirror
- git clone --single-branch --mirror --branch side . dir_mirror_side &&
-
- # --single that does not know what branch to follow
- git checkout two^ &&
- git clone --single-branch . dir_detached &&
-
- # explicit --single with tag
- git clone --single-branch --branch two . dir_tag &&
-
- # advance both "master" and "side" branches
- git checkout side &&
- echo five >file &&
- git commit -a -m five &&
- git checkout master &&
- echo six >file &&
- git commit -a -m six &&
-
- # update tag
- git tag -d two && git tag two
-'
-
-test_expect_success 'by default all branches will be kept updated' '
- (
- cd dir_all && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # follow both master and side
- git for-each-ref refs/heads >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'by default no tags will be kept updated' '
- (
- cd dir_all && git fetch &&
- git for-each-ref refs/tags >../actual
- ) &&
- git for-each-ref refs/tags >expect &&
- test_must_fail test_cmp expect actual
-'
-
-test_expect_success '--single-branch while HEAD pointing at master' '
- (
- cd dir_master && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow master
- git for-each-ref refs/heads/master >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch while HEAD pointing at side' '
- (
- cd dir_side && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow side
- git for-each-ref refs/heads/side >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch side' '
- (
- cd dir_side2 && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow side
- git for-each-ref refs/heads/side >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
- (
- cd dir_tag && git fetch &&
- git for-each-ref refs/tags >../actual
- ) &&
- git for-each-ref refs/tags >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with --mirror' '
- (
- cd dir_mirror && git fetch &&
- git for-each-ref refs > ../actual
- ) &&
- git for-each-ref refs >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch and --mirror' '
- (
- cd dir_mirror_side && git fetch &&
- git for-each-ref refs > ../actual
- ) &&
- git for-each-ref refs >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with detached' '
- (
- cd dir_detached && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # nothing
- >expect &&
- test_cmp expect actual
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
-#
-
-test_description='test transitive info/alternate entries'
-. ./test-lib.sh
-
-# test that a file is not reachable in the current repository
-# but that it is after creating a info/alternate entry
-reachable_via() {
- alternate="$1"
- file="$2"
- if git cat-file -e "HEAD:$file"; then return 1; fi
- echo "$alternate" >> .git/objects/info/alternate
- git cat-file -e "HEAD:$file"
-}
-
-test_valid_repo() {
- git fsck --full > fsck.log &&
- test_line_count = 0 fsck.log
-}
-
-base_dir=$(pwd)
-
-test_expect_success 'preparing first repository' \
-'test_create_repo A && cd A &&
-echo "Hello World" > file1 &&
-git add file1 &&
-git commit -m "Initial commit" file1 &&
-git repack -a -d &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'preparing second repository' \
-'git clone -l -s A B && cd B &&
-echo "foo bar" > file2 &&
-git add file2 &&
-git commit -m "next commit" file2 &&
-git repack -a -d -l &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'preparing third repository' \
-'git clone -l -s B C && cd C &&
-echo "Goodbye, cruel world" > file3 &&
-git add file3 &&
-git commit -m "one more" file3 &&
-git repack -a -d -l &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'creating too deep nesting' \
-'git clone -l -s C D &&
-git clone -l -s D E &&
-git clone -l -s E F &&
-git clone -l -s F G &&
-git clone --bare -l -s G H'
-
-test_expect_success 'invalidity of deepest repository' \
-'cd H && {
- test_valid_repo
- test $? -ne 0
-}'
-
-cd "$base_dir"
-
-test_expect_success 'validity of third repository' \
-'cd C &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'validity of fourth repository' \
-'cd D &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'breaking of loops' \
-'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&&
-cd C &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'that info/alternates is necessary' \
-'cd C &&
-rm -f .git/objects/info/alternates &&
-! (test_valid_repo)'
-
-cd "$base_dir"
-
-test_expect_success 'that relative alternate is possible for current dir' \
-'cd C &&
-echo "../../../B/.git/objects" > .git/objects/info/alternates &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success \
- 'that relative alternate is only possible for current dir' '
- cd D &&
- ! (test_valid_repo)
-'
-
-cd "$base_dir"
-
-test_done
git_bisect () {
git status -su >expect &&
ls -1pR * >>expect &&
- tar czf "$TRASH_DIRECTORY/tmp.tgz" * &&
+ tar cf "$TRASH_DIRECTORY/tmp.tar" * &&
GOOD=$(git rev-parse --verify HEAD) &&
git checkout "$1" &&
echo "foo" >bar &&
git bisect start &&
git bisect good $GOOD &&
rm -rf * &&
- tar xzf "$TRASH_DIRECTORY/tmp.tgz" &&
+ tar xf "$TRASH_DIRECTORY/tmp.tar" &&
git status -su >actual &&
ls -1pR * >>actual &&
test_cmp expect actual &&
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
'
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
test_path_is_missing to_clean
'
-test_expect_success POSIXPERM 'should avoid cleaning possible submodules' '
+test_expect_success POSIXPERM,SANITY 'should avoid cleaning possible submodules' '
rm -fr to_clean possible_sub1 &&
mkdir to_clean possible_sub1 &&
test_when_finished "rm -rf possible_sub*" &&
)
'
+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 &&
git submodule add ../none none &&
test_tick &&
git commit -m "none"
+ ) &&
+ git clone . recursivesuper &&
+ ( cd recursivesuper
+ git submodule add ../super super
)
'
)
'
+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 &&
)
'
+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' '
test_i18ncmp expect actual
'
+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
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' '
(
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 &&
(
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 &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
- test_expect_success 'commit' '
- try_commit "" &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
test_expect_success 'commit --status' '
try_commit --status &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 Y' >file.9y
printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z
>empty
create_merge_msgs () {
git tag c2 &&
c2=$(git rev-parse HEAD) &&
git reset --hard "$c0" &&
+ cp file.9y file &&
+ git add file &&
+ test_tick &&
+ git commit -m "commit 7" &&
+ git tag c7 &&
+ git reset --hard "$c0" &&
cp file.9 file &&
git add file &&
test_tick &&
verify_parents $c1 $c2
'
+test_expect_success 'merge --squash c3 with c7' '
+ git reset --hard c3 &&
+ test_must_fail git merge --squash c7 &&
+ cat result.9z >file &&
+ git commit --no-edit -a &&
+
+ {
+ cat <<-EOF
+ Squashed commit of the following:
+
+ $(git show -s c7)
+
+ # Conflicts:
+ # file
+ EOF
+ } >expect &&
+ git cat-file commit HEAD | sed -e '1,/^$/d' >actual &&
+ test_cmp expect actual
+'
+
test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 and c3' '
test_must_fail git merge -s resolve master
'
+test_expect_success 'merge nothing into void' '
+ git init void &&
+ (
+ cd void &&
+ git remote add up .. &&
+ git fetch up &&
+ test_must_fail git merge FETCH_HEAD
+ )
+'
+
test_done
test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' '
test_must_fail git merge branch 2>out &&
- test_cmp out expect &&
+ test_i18ncmp out expect &&
git commit --allow-empty -m empty &&
(
GIT_MERGE_VERBOSITY=0 &&
export GIT_MERGE_VERBOSITY &&
test_must_fail git merge branch 2>out2
) &&
- test_cmp out2 expect &&
+ test_i18ncmp out2 expect &&
git reset --hard HEAD^
'
four
three
two
-Please, commit your changes or stash them before you can merge.
+Please commit your changes or stash them before you can merge.
error: The following untracked working tree files would be overwritten by merge:
five
Please move or remove them before you can merge.
git add three &&
git add four &&
test_must_fail git merge branch 2>out &&
- test_cmp out expect
+ test_i18ncmp out expect
'
cat >expect <<\EOF
error: Your local changes to the following files would be overwritten by checkout:
rep/one
rep/two
-Please, commit your changes or stash them before you can switch branches.
+Please commit your changes or stash them before you can switch branches.
Aborting
EOF
echo uno >rep/one &&
echo dos >rep/two &&
test_must_fail git checkout branch 2>out &&
- test_cmp out expect
+ test_i18ncmp out expect
'
cat >expect <<\EOF
error: Your local changes to the following files would be overwritten by checkout:
rep/one
rep/two
-Please, commit your changes or stash them before you can switch branches.
+Please commit your changes or stash them before you can switch branches.
Aborting
EOF
test_expect_success 'not uptodate file porcelain checkout error' '
git add rep/one rep/two &&
test_must_fail git checkout branch 2>out &&
- test_cmp out expect
+ test_i18ncmp out expect
'
cat >expect <<\EOF
>rep/untracked-file &&
>rep2/untracked-file &&
test_must_fail git checkout branch 2>out &&
- test_cmp out ../expect
+ test_i18ncmp out ../expect
'
test_done
git reset --hard
'
+test_expect_success 'mergetool delete/delete conflict' '
+ git checkout -b delete-base branch1 &&
+ mkdir -p a/a &&
+ (echo one; echo two; echo 3; echo 4) >a/a/file.txt &&
+ git add a/a/file.txt &&
+ git commit -m"base file" &&
+ git checkout -b move-to-b delete-base &&
+ mkdir -p b/b &&
+ git mv a/a/file.txt b/b/file.txt &&
+ (echo one; echo two; echo 4) >b/b/file.txt &&
+ git commit -a -m"move to b" &&
+ git checkout -b move-to-c delete-base &&
+ mkdir -p c/c &&
+ git mv a/a/file.txt c/c/file.txt &&
+ (echo one; echo two; echo 3) >c/c/file.txt &&
+ git commit -a -m"move to c" &&
+ test_must_fail git merge move-to-b &&
+ echo d | git mergetool a/a/file.txt &&
+ ! test -f a/a/file.txt &&
+ git reset --hard HEAD &&
+ test_must_fail git merge move-to-b &&
+ echo m | git mergetool a/a/file.txt &&
+ test -f b/b/file.txt &&
+ git reset --hard HEAD &&
+ test_must_fail git merge move-to-b &&
+ ! echo a | git mergetool a/a/file.txt &&
+ ! test -f a/a/file.txt &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool produces no errors when keepBackup is used' '
+ test_config mergetool.keepBackup true &&
+ test_must_fail git merge move-to-b &&
+ : >expect &&
+ echo d | git mergetool a/a/file.txt 2>actual &&
+ test_cmp expect actual &&
+ ! test -d a &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool honors tempfile config for deleted files' '
+ test_config mergetool.keepTemporaries false &&
+ test_must_fail git merge move-to-b &&
+ echo d | git mergetool a/a/file.txt &&
+ ! test -d a &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' '
+ test_config mergetool.keepTemporaries true &&
+ test_must_fail git merge move-to-b &&
+ ! (echo a; echo n) | git mergetool a/a/file.txt &&
+ test -d a/a &&
+ cat >expect <<-\EOF &&
+ file_BASE_.txt
+ file_LOCAL_.txt
+ file_REMOTE_.txt
+ EOF
+ ls -1 a/a | sed -e "s/[0-9]*//g" >actual &&
+ test_cmp expect actual &&
+ git clean -fdx &&
+ git reset --hard HEAD
+'
+
test_expect_success 'deleted vs modified submodule' '
git checkout -b test6 branch1 &&
git submodule update -N &&
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
)
'
+test_expect_success 'grep --no-index descends into repos, but not .git' '
+ rm -fr non &&
+ mkdir -p non/git &&
+ (
+ GIT_CEILING_DIRECTORIES="$(pwd)/non" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd non/git &&
+
+ echo magic >file &&
+ git init repo &&
+ (
+ cd repo &&
+ echo magic >file &&
+ git add file &&
+ git commit -m foo &&
+ echo magic >.git/file
+ ) &&
+
+ cat >expect <<-\EOF &&
+ file
+ repo/file
+ EOF
+ git grep -l --no-index magic >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'setup double-dash tests' '
cat >double-dash <<EOF &&
--
grep "A U Thor" actual
'
+test_expect_success 'blame file with CRLF core.autocrlf=true' '
+ git config core.autocrlf false &&
+ printf "testcase\r\n" >crlfinrepo &&
+ >.gitattributes &&
+ git add crlfinrepo &&
+ git commit -m "add crlfinrepo" &&
+ git config core.autocrlf true &&
+ mv crlfinrepo tmp &&
+ git checkout crlfinrepo &&
+ rm tmp &&
+ git blame crlfinrepo >actual &&
+ grep "A U Thor" actual
+'
+
test_done
git fast-import </dev/null
'
+test_expect_success 'truncated stream complains' '
+ echo "tag foo" | test_must_fail git fast-import
+'
+
test_expect_success 'A: create pack from stdin' '
test_tick &&
cat >input <<-INPUT_END &&
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
}
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" &&
# 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 &&
'
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" &&
(
test_cmp expect.rev actual.rev
}
-# Print a sequence of numbers or letters in increasing order. This is
-# similar to GNU seq(1), but the latter might not be available
-# everywhere (and does not do letters). It may be used like:
-#
-# for i in $(test_seq 100)
-# do
-# for j in $(test_seq 10 20)
-# do
-# for k in $(test_seq a z)
-# do
-# echo $i-$j-$k
-# done
-# done
-# done
+# Print a sequence of integers in increasing order, either with
+# two arguments (start and end):
+#
+# test_seq 1 5 -- outputs 1 2 3 4 5 one line at a time
+#
+# or with one argument (end), in which case it starts counting
+# from 1.
test_seq () {
case $# in
2) ;;
*) error "bug in the test script: not 1 or 2 parameters to test_seq" ;;
esac
- perl -le 'print for $ARGV[0]..$ARGV[1]' -- "$@"
+ test_seq_counter__=$1
+ while test "$test_seq_counter__" -le "$2"
+ do
+ echo "$test_seq_counter__"
+ test_seq_counter__=$(( $test_seq_counter__ + 1 ))
+ done
}
# This function can be used to schedule some commands to be run
}
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
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
exec 4>/dev/null 3>/dev/null
fi
+# Send any "-x" output directly to stderr to avoid polluting tests
+# which capture stderr. We can do this unconditionally since it
+# has no effect if tracing isn't turned on.
+#
+# Note that this sets up the trace fd as soon as we assign the variable, so it
+# must come after the creation of descriptor 4 above. Likewise, we must never
+# unset this, as it has the side effect of closing descriptor 4, which we
+# use to show verbose tests to the user.
+#
+# Note also that we don't need or want to export it. The tracing is local to
+# this shell, and we would not want to influence any shells we exec.
+BASH_XTRACEFD=4
+
test_failure=0
test_count=0
test_fixed=0
unsigned char hash1[20], hash2[20], shifted[20];
struct tree *one, *two;
+ setup_git_directory();
+
if (get_sha1(av[1], hash1))
die("cannot parse %s as an object name", av[1]);
if (get_sha1(av[2], hash2))
if (argc < 2)
return 1;
+ setup_git_directory();
+
if (!strcmp(argv[1], "run-twice")) {
printf("1st\n");
if (!run_revision_walk())
}
/*
- * Tries to read read data from source into buffer. If buffer is full,
+ * Tries to read data from source into buffer. If buffer is full,
* no data is read. Returns 0 on success, -1 on error.
*/
static int udt_do_read(struct unidirectional_transfer *t)
int i;
const char **msgs = opts->msgs;
const char *msg;
- const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
- if (advice_commit_before_merge)
- msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
- "Please, commit your changes or stash them before you can %s.";
+ if (!strcmp(cmd, "checkout"))
+ msg = advice_commit_before_merge
+ ? _("Your local changes to the following files would be overwritten by checkout:\n%%s"
+ "Please commit your changes or stash them before you can switch branches.")
+ : _("Your local changes to the following files would be overwritten by checkout:\n%%s");
+ else if (!strcmp(cmd, "merge"))
+ msg = advice_commit_before_merge
+ ? _("Your local changes to the following files would be overwritten by merge:\n%%s"
+ "Please commit your changes or stash them before you can merge.")
+ : _("Your local changes to the following files would be overwritten by merge:\n%%s");
else
- msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
+ msg = advice_commit_before_merge
+ ? _("Your local changes to the following files would be overwritten by %s:\n%%s"
+ "Please commit your changes or stash them before you can %s.")
+ : _("Your local changes to the following files would be overwritten by %s:\n%%s");
msgs[ERROR_WOULD_OVERWRITE] = msgs[ERROR_NOT_UPTODATE_FILE] =
- xstrfmt(msg, cmd, cmd2);
+ xstrfmt(msg, cmd, cmd);
msgs[ERROR_NOT_UPTODATE_DIR] =
- "Updating the following directories would lose untracked files in it:\n%s";
-
- if (advice_commit_before_merge)
- msg = "The following untracked working tree files would be %s by %s:\n%%s"
- "Please move or remove them before you can %s.";
+ _("Updating the following directories would lose untracked files in it:\n%s");
+
+ if (!strcmp(cmd, "checkout"))
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be removed by checkout:\n%%s"
+ "Please move or remove them before you can switch branches.")
+ : _("The following untracked working tree files would be removed by checkout:\n%%s");
+ else if (!strcmp(cmd, "merge"))
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be removed by merge:\n%%s"
+ "Please move or remove them before you can merge.")
+ : _("The following untracked working tree files would be removed by merge:\n%%s");
else
- msg = "The following untracked working tree files would be %s by %s:\n%%s";
-
- msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, "removed", cmd, cmd2);
- msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, "overwritten", cmd, cmd2);
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be removed by %s:\n%%s"
+ "Please move or remove them before you can %s.")
+ : _("The following untracked working tree files would be removed by %s:\n%%s");
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = xstrfmt(msg, cmd, cmd);
+
+ if (!strcmp(cmd, "checkout"))
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be overwritten by checkout:\n%%s"
+ "Please move or remove them before you can switch branches.")
+ : _("The following untracked working tree files would be overwritten by checkout:\n%%s");
+ else if (!strcmp(cmd, "merge"))
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be overwritten by merge:\n%%s"
+ "Please move or remove them before you can merge.")
+ : _("The following untracked working tree files would be overwritten by merge:\n%%s");
+ else
+ msg = advice_commit_before_merge
+ ? _("The following untracked working tree files would be overwritten by %s:\n%%s"
+ "Please move or remove them before you can %s.")
+ : _("The following untracked working tree files would be overwritten by %s:\n%%s");
+ msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = xstrfmt(msg, cmd, cmd);
/*
* Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
* cannot easily display it as a list.
*/
- msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'. Cannot bind.";
+ msgs[ERROR_BIND_OVERLAP] = _("Entry '%s' overlaps with '%s'. Cannot bind.");
msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
- "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
+ _("Cannot update sparse checkout: the following entries are not up-to-date:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
- "The following Working tree files would be overwritten by sparse checkout update:\n%s";
+ _("The following Working tree files would be overwritten by sparse checkout update:\n%s");
msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
- "The following Working tree files would be removed by sparse checkout update:\n%s";
+ _("The following Working tree files would be removed by sparse checkout update:\n%s");
opts->show_all_errors = 1;
/* rejected paths may not have a static buffer */
string_list_clear(rejects, 0);
}
if (something_displayed)
- fprintf(stderr, "Aborting\n");
+ fprintf(stderr, _("Aborting\n"));
}
/*
"corruption on the remote side.";
int buffered = -1;
ssize_t sz;
- const char *argv[13];
- int i, arg = 0;
+ int i;
FILE *pipe_fd;
if (shallow_nr) {
- argv[arg++] = "--shallow-file";
- argv[arg++] = "";
+ argv_array_push(&pack_objects.args, "--shallow-file");
+ argv_array_push(&pack_objects.args, "");
}
- argv[arg++] = "pack-objects";
- argv[arg++] = "--revs";
+ argv_array_push(&pack_objects.args, "pack-objects");
+ argv_array_push(&pack_objects.args, "--revs");
if (use_thin_pack)
- argv[arg++] = "--thin";
+ argv_array_push(&pack_objects.args, "--thin");
- argv[arg++] = "--stdout";
+ argv_array_push(&pack_objects.args, "--stdout");
if (shallow_nr)
- argv[arg++] = "--shallow";
+ argv_array_push(&pack_objects.args, "--shallow");
if (!no_progress)
- argv[arg++] = "--progress";
+ argv_array_push(&pack_objects.args, "--progress");
if (use_ofs_delta)
- argv[arg++] = "--delta-base-offset";
+ argv_array_push(&pack_objects.args, "--delta-base-offset");
if (use_include_tag)
- argv[arg++] = "--include-tag";
- argv[arg++] = NULL;
+ argv_array_push(&pack_objects.args, "--include-tag");
pack_objects.in = -1;
pack_objects.out = -1;
pack_objects.err = -1;
pack_objects.git_cmd = 1;
- pack_objects.argv = argv;
if (start_command(&pack_objects))
die("git upload-pack: unable to fork git-pack-objects");
int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
/*
- * Returns true if the the path would match ".git" after HFS case-folding.
+ * Returns true if the path would match ".git" after HFS case-folding.
* The path should be NUL-terminated, but we will match variants of both ".git\0"
* and ".git/..." (but _not_ ".../.git"). This makes it suitable for both fsck
* and verify_path().
/*
* Try to advance faster when an asterisk is
* followed by a literal. We know in this case
- * that the the string before the literal
+ * that the string before the literal
* must belong to "*".
* If match_slash is false, do not look past
* the first slash as it cannot belong to '*'.
/*
* read 'path_to_ref' into 'ref'. Also if is_detached is not NULL,
- * set is_detached to 1 (0) if the ref is detatched (is not detached).
+ * set is_detached to 1 (0) if the ref is detached (is not detached).
*
* $GIT_COMMON_DIR/$symref (e.g. HEAD) is practically outside $GIT_DIR so
* for linked worktrees, `resolve_ref_unsafe()` won't work (it uses
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
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)
xdl_free_ctx(&xe->xdf2);
xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
return -1;
}
- if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+ if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)
xdl_free_classifier(&cf);
return 0;