Imported Upstream version 2.8.3 upstream/2.8.3
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:15:46 +0000 (15:15 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:15:46 +0000 (15:15 +0900)
90 files changed:
.mailmap
.travis.yml
Documentation/RelNotes/2.8.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-config.txt
Documentation/git-for-each-ref.txt
Documentation/git-pack-objects.txt
Documentation/git-repack.txt
Documentation/git.txt
Documentation/glossary-content.txt
Documentation/technical/api-config.txt
GIT-VERSION-GEN
Makefile
RelNotes
branch.c
branch.h
builtin/apply.c
builtin/branch.c
builtin/commit.c
builtin/init-db.c
builtin/mv.c
builtin/notes.c
builtin/pack-objects.c
builtin/replace.c
builtin/submodule--helper.c
bundle.c
cache.h
compat/apple-common-crypto.h
compat/mingw.h
compat/win32/pthread.h
compat/win32mmap.c
config.c
config.mak.uname
configure.ac
contrib/hooks/multimail/CHANGES
contrib/hooks/multimail/CONTRIBUTING.rst
contrib/hooks/multimail/README
contrib/hooks/multimail/README.Git
contrib/hooks/multimail/doc/customizing-emails.rst [new file with mode: 0644]
contrib/hooks/multimail/doc/troubleshooting.rst [new file with mode: 0644]
contrib/hooks/multimail/git_multimail.py
contrib/hooks/multimail/post-receive.example
credential-cache.c
dir.c
dir.h
environment.c
fetch-pack.c
git-compat-util.h
git-difftool--helper.sh
git-mergetool--lib.sh
git-mergetool.sh
git-p4.py
git-send-email.perl
git-submodule.sh
gitweb/gitweb.perl
http.c
ident.c
imap-send.c
path.c
refs.h
refs/files-backend.c
remote.c
run-command.c
run-command.h
send-pack.c
setup.c
string-list.c
submodule-config.c
t/lib-git-p4.sh
t/t1020-subdirectory.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3404-rebase-interactive.sh
t/t5504-fetch-receive-strict.sh
t/t5510-fetch.sh
t/t5532-fetch-proxy.sh
t/t7001-mv.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7501-commit.sh
t/t7800-difftool.sh
t/t9802-git-p4-filetype.sh
t/t9824-git-p4-git-lfs.sh
t/test-lib.sh
wrapper.c
wt-status.c

index e5b4126..a9162c0 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -51,6 +51,7 @@ Dirk Süsserott <newsletter@dirk.my1.cc>
 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>
index 78e433b..1fdcec8 100644 (file)
@@ -22,8 +22,11 @@ addons:
 env:
   global:
     - DEVELOPER=1
-    - P4_VERSION="15.2"
-    - GIT_LFS_VERSION="1.1.0"
+    # The Linux build installs the defined dependency versions below.
+    # The OS X build installs the latest available versions. Keep that
+    # in mind when you encounter a broken OS X build!
+    - LINUX_P4_VERSION="16.1"
+    - LINUX_GIT_LFS_VERSION="1.2.0"
     - DEFAULT_TEST_TARGET=prove
     - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save"
     - GIT_TEST_OPTS="--verbose --tee"
@@ -38,17 +41,17 @@ before_install:
     linux)
       mkdir --parents custom/p4
       pushd custom/p4
-        wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4d
-        wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4
+        wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4d
+        wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4
         chmod u+x p4d
         chmod u+x p4
         export PATH="$(pwd):$PATH"
       popd
       mkdir --parents custom/git-lfs
       pushd custom/git-lfs
-        wget --quiet https://github.com/github/git-lfs/releases/download/v$GIT_LFS_VERSION/git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz
-        tar --extract --gunzip --file "git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz"
-        cp git-lfs-$GIT_LFS_VERSION/git-lfs .
+        wget --quiet https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz
+        tar --extract --gunzip --file "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz"
+        cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs .
         export PATH="$(pwd):$PATH"
       popd
       ;;
diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt
new file mode 100644 (file)
index 0000000..fedd996
--- /dev/null
@@ -0,0 +1,101 @@
+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.
index 98fc4cc..e8ad978 100644 (file)
@@ -61,23 +61,28 @@ Make sure that you have tests for the bug you are fixing.  See
 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
@@ -370,6 +375,47 @@ Know the status of your patch after submission
   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
 
index 2cd6bdd..ef34787 100644 (file)
@@ -2147,8 +2147,11 @@ pack.packSizeLimit::
        The maximum size of a pack.  This setting only affects
        packing to a file when repacking, i.e. the git:// protocol
        is unaffected.  It can be overridden by the `--max-pack-size`
-       option of linkgit:git-repack[1]. The minimum size allowed is
-       limited to 1 MiB. The default is unlimited.
+       option of linkgit:git-repack[1].  Reaching this limit results
+       in the creation of multiple packfiles; which in turn prevents
+       bitmaps from being created.
+       The minimum size allowed is limited to 1 MiB.
+       The default is unlimited.
        Common unit suffixes of 'k', 'm', or 'g' are
        supported.
 
@@ -2548,8 +2551,9 @@ repack.writeBitmaps::
        objects to disk (e.g., when `git repack -a` is run).  This
        index can speed up the "counting objects" phase of subsequent
        packs created for clones and fetches, at the cost of some disk
-       space and extra time spent on the initial repack.  Defaults to
-       false.
+       space and extra time spent on the initial repack.  This has
+       no effect if multiple packfiles are created.
+       Defaults to false.
 
 rerere.autoUpdate::
        When set to true, `git-rerere` updates the index with the
index 32f48ed..4b0318e 100644 (file)
@@ -26,12 +26,12 @@ ifndef::git-format-patch[]
 ifdef::git-diff[]
        This is the default.
 endif::git-diff[]
-endif::git-format-patch[]
 
 -s::
 --no-patch::
        Suppress diff output. Useful for commands like `git show` that
        show the patch by default, or to cancel the effect of `--patch`.
+endif::git-format-patch[]
 
 -U<n>::
 --unified=<n>::
index 6fc08e3..6843114 100644 (file)
@@ -58,10 +58,10 @@ that location (you can say '--local' but that is the default).
 This command will fail with non-zero status upon error.  Some exit
 codes are:
 
-- The config file is invalid (ret=3),
-- can not write to the config file (ret=4),
+- The section or key is invalid (ret=1),
 - no section or name was provided (ret=2),
-- the section or key is invalid (ret=1),
+- the config file is invalid (ret=3),
+- the config file cannot be written (ret=4),
 - you try to unset an option which does not exist (ret=5),
 - you try to unset/set an option for which multiple lines match (ret=5), or
 - you try to use an invalid regexp (ret=6).
index 012e8f9..c52578b 100644 (file)
@@ -76,7 +76,7 @@ OPTIONS
        specified commit (HEAD if not specified).
 
 --contains [<object>]::
-       Only list tags which contain the specified commit (HEAD if not
+       Only list refs which contain the specified commit (HEAD if not
        specified).
 
 FIELD NAMES
index bbea529..19cdcd0 100644 (file)
@@ -110,7 +110,8 @@ base-name::
 --max-pack-size=<n>::
        Maximum size of each output pack file. The size can be suffixed with
        "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
-       If specified,  multiple packfiles may be created.
+       If specified, multiple packfiles may be created, which also
+       prevents the creation of a bitmap index.
        The default is unlimited, unless the config variable
        `pack.packSizeLimit` is set.
 
index af230d0..b9c02ce 100644 (file)
@@ -106,7 +106,8 @@ other objects in that pack they already have locally.
 --max-pack-size=<n>::
        Maximum size of each output pack file. The size can be suffixed with
        "k", "m", or "g". The minimum size allowed is limited to 1 MiB.
-       If specified,  multiple packfiles may be created.
+       If specified, multiple packfiles may be created, which also
+       prevents the creation of a bitmap index.
        The default is unlimited, unless the config variable
        `pack.packSizeLimit` is set.
 
@@ -115,7 +116,8 @@ other objects in that pack they already have locally.
        Write a reachability bitmap index as part of the repack. This
        only makes sense when used with `-a` or `-A`, as the bitmaps
        must be able to refer to all reachable objects. This option
-       overrides the setting of `pack.writeBitmaps`.
+       overrides the setting of `repack.writeBitmaps`.  This option
+       has no effect if multiple packfiles are created.
 
 --pack-kept-objects::
        Include objects in `.keep` files when repacking.  Note that we
@@ -123,7 +125,7 @@ other objects in that pack they already have locally.
        This means that we may duplicate objects, but this makes the
        option safe to use when there are concurrent pushes or fetches.
        This option is generally only useful if you are writing bitmaps
-       with `-b` or `pack.writeBitmaps`, as it ensures that the
+       with `-b` or `repack.writeBitmaps`, as it ensures that the
        bitmapped packfile has the necessary objects.
 
 Configuration
index 34ff007..dd6dbf7 100644 (file)
@@ -43,11 +43,12 @@ unreleased) version of Git, that is available from the 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v2.8.2/git.html[documentation for release 2.8.2]
+* link:v2.8.3/git.html[documentation for release 2.8.3]
 
 * release notes for
-  link:RelNotes/2.8.2.txt[2.8.2].
-  link:RelNotes/2.8.1.txt[2.8.1].
+  link:RelNotes/2.8.3.txt[2.8.3],
+  link:RelNotes/2.8.2.txt[2.8.2],
+  link:RelNotes/2.8.1.txt[2.8.1],
   link:RelNotes/2.8.0.txt[2.8].
 
 * link:v2.7.3/git.html[documentation for release 2.7.3]
index cafc284..8ad29e6 100644 (file)
@@ -145,7 +145,7 @@ current branch integrates with) obviously do not work, as there is no
        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
index 0d8b99b..20741f3 100644 (file)
@@ -63,13 +63,6 @@ parse for configuration, rather than looking in the usual files. Regular
 Specify whether include directives should be followed in parsed files.
 Regular `git_config` defaults to `1`.
 
-There is a special version of `git_config` called `git_config_early`.
-This version takes an additional parameter to specify the repository
-config, instead of having it looked up via `git_path`. This is useful
-early in a Git program before the repository has been found. Unless
-you're working with early setup code, you probably don't want to use
-this.
-
 Reading Specific Files
 ----------------------
 
index 5f99f23..596965a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.8.2
+DEF_VER=v2.8.3
 
 LF='
 '
index c7354bf..a83e322 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -355,9 +355,6 @@ all::
 #
 # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt.
 #
-# Define NO_HMAC_CTX_CLEANUP if your OpenSSL is version 0.9.6b or earlier to
-# cleanup the HMAC context with the older HMAC_cleanup function.
-#
 # Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
 # compiles the following initialization:
 #
@@ -1138,9 +1135,6 @@ ifndef NO_OPENSSL
        ifdef NEEDS_CRYPTO_WITH_SSL
                OPENSSL_LIBSSL += -lcrypto
        endif
-       ifdef NO_HMAC_CTX_CLEANUP
-               BASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP
-       endif
 else
        BASIC_CFLAGS += -DNO_OPENSSL
        BLK_SHA1 = 1
index 04710da..eb82eea 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.8.2.txt
\ No newline at end of file
+Documentation/RelNotes/2.8.3.txt
\ No newline at end of file
index c50ea42..4162443 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -344,3 +344,26 @@ void die_if_checked_out(const char *branch)
                die(_("'%s' is already checked out at '%s'"), branch, existing);
        }
 }
+
+int replace_each_worktree_head_symref(const char *oldref, const char *newref)
+{
+       int ret = 0;
+       struct worktree **worktrees = get_worktrees();
+       int i;
+
+       for (i = 0; worktrees[i]; i++) {
+               if (worktrees[i]->is_detached)
+                       continue;
+               if (strcmp(oldref, worktrees[i]->head_ref))
+                       continue;
+
+               if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {
+                       ret = -1;
+                       error(_("HEAD of working tree %s is not updated"),
+                             worktrees[i]->path);
+               }
+       }
+
+       free_worktrees(worktrees);
+       return ret;
+}
index 78ad438..d69163d 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -60,4 +60,11 @@ extern int read_branch_desc(struct strbuf *, const char *branch_name);
  */
 extern void die_if_checked_out(const char *branch);
 
+/*
+ * Update all per-worktree HEADs pointing at the old ref to point the new ref.
+ * This will be used when renaming a branch. Returns 0 if successful, non-zero
+ * otherwise.
+ */
+extern int replace_each_worktree_head_symref(const char *oldref, const char *newref);
+
 #endif
index 42c610e..ce3b778 100644 (file)
@@ -931,22 +931,19 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
                return find_name(line, NULL, p_value, TERM_TAB);
 
        if (orig_name) {
-               int len;
-               const char *name;
+               int len = strlen(orig_name);
                char *another;
-               name = orig_name;
-               len = strlen(name);
                if (isnull)
-                       die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
+                       die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
+                           orig_name, linenr);
                another = find_name(line, NULL, p_value, TERM_TAB);
-               if (!another || memcmp(another, name, len + 1))
+               if (!another || memcmp(another, orig_name, len + 1))
                        die((side == DIFF_NEW_NAME) ?
                            _("git apply: bad git-diff - inconsistent new filename on line %d") :
                            _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
                free(another);
                return orig_name;
-       }
-       else {
+       } else {
                /* expect "/dev/null" */
                if (memcmp("/dev/null", line, 9) || line[9] != '\n')
                        die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
@@ -956,21 +953,15 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
 
 static int gitdiff_oldname(const char *line, struct patch *patch)
 {
-       char *orig = patch->old_name;
        patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
                                              DIFF_OLD_NAME);
-       if (orig != patch->old_name)
-               free(orig);
        return 0;
 }
 
 static int gitdiff_newname(const char *line, struct patch *patch)
 {
-       char *orig = patch->new_name;
        patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
                                              DIFF_NEW_NAME);
-       if (orig != patch->new_name)
-               free(orig);
        return 0;
 }
 
@@ -1872,6 +1863,11 @@ static struct fragment *parse_binary_hunk(char **buf_p,
        return NULL;
 }
 
+/*
+ * Returns:
+ *   -1 in case of error,
+ *   the length of the parsed binary patch otherwise
+ */
 static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
 {
        /*
@@ -2017,6 +2013,8 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
                        linenr++;
                        used = parse_binary(buffer + hd + llen,
                                            size - hd - llen, patch);
+                       if (used < 0)
+                               return -1;
                        if (used)
                                patchsize = used + llen;
                        else
@@ -4373,8 +4371,10 @@ static int apply_patch(int fd, const char *filename, int options)
                patch->inaccurate_eof = !!(options & INACCURATE_EOF);
                patch->recount =  !!(options & RECOUNT);
                nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
-               if (nr < 0)
+               if (nr < 0) {
+                       free_patch(patch);
                        break;
+               }
                if (apply_in_reverse)
                        reverse_patches(patch);
                if (use_patch(patch)) {
index 7b45b6b..0adba62 100644 (file)
@@ -20,6 +20,7 @@
 #include "utf8.h"
 #include "wt-status.h"
 #include "ref-filter.h"
+#include "worktree.h"
 
 static const char * const builtin_branch_usage[] = {
        N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
@@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
                int flags = 0;
 
                strbuf_branchname(&bname, argv[i]);
-               if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {
-                       error(_("Cannot delete the branch '%s' "
-                             "which you are currently on."), bname.buf);
-                       ret = 1;
-                       continue;
-               }
-
                free(name);
-
                name = mkpathdup(fmt, bname.buf);
+
+               if (kinds == FILTER_REFS_BRANCHES) {
+                       char *worktree = find_shared_symref("HEAD", name);
+                       if (worktree) {
+                               error(_("Cannot delete branch '%s' "
+                                       "checked out at '%s'"),
+                                     bname.buf, worktree);
+                               free(worktree);
+                               ret = 1;
+                               continue;
+                       }
+               }
+
                target = resolve_ref_unsafe(name,
                                            RESOLVE_REF_READING
                                            | RESOLVE_REF_NO_RECURSE
@@ -393,22 +399,25 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
        int current = 0;
        int color;
        struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
-       const char *prefix = "";
+       const char *prefix_to_show = "";
+       const char *prefix_to_skip = NULL;
        const char *desc = item->refname;
        char *to_free = NULL;
 
        switch (item->kind) {
        case FILTER_REFS_BRANCHES:
-               skip_prefix(desc, "refs/heads/", &desc);
+               prefix_to_skip = "refs/heads/";
+               skip_prefix(desc, prefix_to_skip, &desc);
                if (!filter->detached && !strcmp(desc, head))
                        current = 1;
                else
                        color = BRANCH_COLOR_LOCAL;
                break;
        case FILTER_REFS_REMOTES:
-               skip_prefix(desc, "refs/remotes/", &desc);
+               prefix_to_skip = "refs/remotes/";
+               skip_prefix(desc, prefix_to_skip, &desc);
                color = BRANCH_COLOR_REMOTE;
-               prefix = remote_prefix;
+               prefix_to_show = remote_prefix;
                break;
        case FILTER_REFS_DETACHED_HEAD:
                desc = to_free = get_head_description();
@@ -425,7 +434,7 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
                color = BRANCH_COLOR_CURRENT;
        }
 
-       strbuf_addf(&name, "%s%s", prefix, desc);
+       strbuf_addf(&name, "%s%s", prefix_to_show, desc);
        if (filter->verbose) {
                int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
                strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
@@ -436,8 +445,10 @@ static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
                            name.buf, branch_get_color(BRANCH_COLOR_RESET));
 
        if (item->symref) {
-               skip_prefix(item->symref, "refs/remotes/", &desc);
-               strbuf_addf(&out, " -> %s", desc);
+               const char *symref = item->symref;
+               if (prefix_to_skip)
+                       skip_prefix(symref, prefix_to_skip, &symref);
+               strbuf_addf(&out, " -> %s", symref);
        }
        else if (filter->verbose)
                /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
@@ -552,8 +563,7 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        if (recovery)
                warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
 
-       /* no need to pass logmsg here as HEAD didn't really move */
-       if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+       if (replace_each_worktree_head_symref(oldref.buf, newref.buf))
                die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
 
        strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
index c733ec9..e133037 100644 (file)
@@ -694,7 +694,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                }
        }
 
-       if (message.len) {
+       if (have_option_m) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
@@ -1171,9 +1171,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
                f++;
        if (f > 1)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
-       if (message.len && f > 0)
+       if (have_option_m && f > 0)
                die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
-       if (f || message.len)
+       if (f || have_option_m)
                template_file = NULL;
        if (edit_message)
                use_message = edit_message;
index da531f6..b2d8d40 100644 (file)
@@ -95,6 +95,8 @@ static void copy_templates(const char *template_dir)
        struct strbuf path = STRBUF_INIT;
        struct strbuf template_path = STRBUF_INIT;
        size_t template_len;
+       struct repository_format template_format;
+       struct strbuf err = STRBUF_INIT;
        DIR *dir;
        char *to_free = NULL;
 
@@ -121,17 +123,18 @@ static void copy_templates(const char *template_dir)
 
        /* Make sure that template is from the correct vintage */
        strbuf_addstr(&template_path, "config");
-       repository_format_version = 0;
-       git_config_from_file(check_repository_format_version,
-                            template_path.buf, NULL);
+       read_repository_format(&template_format, template_path.buf);
        strbuf_setlen(&template_path, template_len);
 
-       if (repository_format_version &&
-           repository_format_version != GIT_REPO_VERSION) {
-               warning(_("not copying templates of "
-                       "a wrong format version %d from '%s'"),
-                       repository_format_version,
-                       template_dir);
+       /*
+        * No mention of version at all is OK, but anything else should be
+        * verified.
+        */
+       if (template_format.version >= 0 &&
+           verify_repository_format(&template_format, &err) < 0) {
+               warning(_("not copying templates from '%s': %s"),
+                         template_dir, err.buf);
+               strbuf_release(&err);
                goto close_free_return;
        }
 
@@ -199,13 +202,13 @@ static int create_default_files(const char *template_path)
 
        /* reading existing config may have overwrote it */
        if (init_shared_repository != -1)
-               shared_repository = init_shared_repository;
+               set_shared_repository(init_shared_repository);
 
        /*
         * We would have created the above under user's umask -- under
         * shared-repository settings, we would need to fix them up.
         */
-       if (shared_repository) {
+       if (get_shared_repository()) {
                adjust_shared_perm(get_git_dir());
                adjust_shared_perm(git_path_buf(&buf, "refs"));
                adjust_shared_perm(git_path_buf(&buf, "refs/heads"));
@@ -370,7 +373,7 @@ int init_db(const char *template_dir, unsigned int flags)
 
        create_object_directory();
 
-       if (shared_repository) {
+       if (get_shared_repository()) {
                char buf[10];
                /* We do not spell "group" and such, so that
                 * the configuration can be read by older version
@@ -378,12 +381,12 @@ int init_db(const char *template_dir, unsigned int flags)
                 * and compatibility values for PERM_GROUP and
                 * PERM_EVERYBODY.
                 */
-               if (shared_repository < 0)
+               if (get_shared_repository() < 0)
                        /* force to the mode value */
-                       xsnprintf(buf, sizeof(buf), "0%o", -shared_repository);
-               else if (shared_repository == PERM_GROUP)
+                       xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository());
+               else if (get_shared_repository() == PERM_GROUP)
                        xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
-               else if (shared_repository == PERM_EVERYBODY)
+               else if (get_shared_repository() == PERM_EVERYBODY)
                        xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
                else
                        die("BUG: invalid value for shared_repository");
@@ -399,7 +402,7 @@ int init_db(const char *template_dir, unsigned int flags)
                   "", and the last '%s%s' is the verbatim directory name. */
                printf(_("%s%s Git repository in %s%s\n"),
                       reinit ? _("Reinitialized existing") : _("Initialized empty"),
-                      shared_repository ? _(" shared") : "",
+                      get_shared_repository() ? _(" shared") : "",
                       git_dir, len && git_dir[len-1] != '/' ? "/" : "");
        }
 
@@ -494,8 +497,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                                 * and we know shared_repository should always be 0;
                                 * but just in case we play safe.
                                 */
-                               saved = shared_repository;
-                               shared_repository = 0;
+                               saved = get_shared_repository();
+                               set_shared_repository(0);
                                switch (safe_create_leading_directories_const(argv[0])) {
                                case SCLD_OK:
                                case SCLD_PERMS:
@@ -507,7 +510,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                                        die_errno(_("cannot mkdir %s"), argv[0]);
                                        break;
                                }
-                               shared_repository = saved;
+                               set_shared_repository(saved);
                                if (mkdir(argv[0], 0777) < 0)
                                        die_errno(_("cannot mkdir %s"), argv[0]);
                                mkdir_tried = 1;
@@ -525,7 +528,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        }
 
        if (init_shared_repository != -1)
-               shared_repository = init_shared_repository;
+               set_shared_repository(init_shared_repository);
 
        /*
         * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
index aeae855..a201426 100644 (file)
@@ -252,15 +252,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                int pos;
                if (show_only || verbose)
                        printf(_("Renaming %s to %s\n"), src, dst);
-               if (!show_only && mode != INDEX) {
-                       if (rename(src, dst) < 0 && !ignore_errors)
-                               die_errno(_("renaming '%s' failed"), src);
-                       if (submodule_gitfile[i]) {
-                               if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
-                                       connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
-                               if (!update_path_in_gitmodules(src, dst))
-                                       gitmodules_modified = 1;
-                       }
+               if (show_only)
+                       continue;
+               if (mode != INDEX && rename(src, dst) < 0) {
+                       if (ignore_errors)
+                               continue;
+                       die_errno(_("renaming '%s' failed"), src);
+               }
+               if (submodule_gitfile[i]) {
+                       if (submodule_gitfile[i] != SUBMODULE_WITH_GITDIR)
+                               connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
+                       if (!update_path_in_gitmodules(src, dst))
+                               gitmodules_modified = 1;
                }
 
                if (mode == WORKING_DIRECTORY)
index ed6f222..6fd058d 100644 (file)
@@ -744,13 +744,14 @@ static int merge_commit(struct notes_merge_options *o)
 static int git_config_get_notes_strategy(const char *key,
                                         enum notes_merge_strategy *strategy)
 {
-       const char *value;
+       char *value;
 
-       if (git_config_get_string_const(key, &value))
+       if (git_config_get_string(key, &value))
                return 1;
        if (parse_notes_merge_strategy(value, strategy))
                git_die_config(key, "unknown notes merge strategy %s", value);
 
+       free(value);
        return 0;
 }
 
index a27de5b..b6664ce 100644 (file)
@@ -759,6 +759,10 @@ static off_t write_reused_pack(struct sha1file *f)
        return reuse_packfile_offset - sizeof(struct pack_header);
 }
 
+static const char no_split_warning[] = N_(
+"disabling bitmap writing, packs are split due to pack.packSizeLimit"
+);
+
 static void write_pack_file(void)
 {
        uint32_t i = 0, j;
@@ -813,7 +817,10 @@ static void write_pack_file(void)
                        fixup_pack_header_footer(fd, sha1, pack_tmp_name,
                                                 nr_written, sha1, offset);
                        close(fd);
-                       write_bitmap_index = 0;
+                       if (write_bitmap_index) {
+                               warning(_(no_split_warning));
+                               write_bitmap_index = 0;
+                       }
                }
 
                if (!pack_to_stdout) {
index 748c6ca..b58c714 100644 (file)
@@ -440,6 +440,7 @@ int cmd_replace(int argc, const char **argv, const char *prefix)
        };
 
        check_replace_refs = 0;
+       git_config(git_default_config, NULL);
 
        argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
 
index 5295b72..3bea3aa 100644 (file)
@@ -147,11 +147,11 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
 
 static int module_clone(int argc, const char **argv, const char *prefix)
 {
-       const char *path = NULL, *name = NULL, *url = NULL;
+       const char *name = NULL, *url = NULL;
        const char *reference = NULL, *depth = NULL;
        int quiet = 0;
        FILE *submodule_dot_git;
-       char *sm_gitdir, *cwd, *p;
+       char *p, *path = NULL, *sm_gitdir;
        struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
 
@@ -188,8 +188,18 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        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)
@@ -206,45 +216,30 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        }
 
        /* Write a .git file in the submodule to redirect to the superproject. */
-       if (safe_create_leading_directories_const(path) < 0)
-               die(_("could not create directory '%s'"), path);
-
-       if (path && *path)
-               strbuf_addf(&sb, "%s/.git", path);
-       else
-               strbuf_addstr(&sb, ".git");
-
+       strbuf_addf(&sb, "%s/.git", path);
        if (safe_create_leading_directories_const(sb.buf) < 0)
                die(_("could not create leading directories of '%s'"), sb.buf);
        submodule_dot_git = fopen(sb.buf, "w");
        if (!submodule_dot_git)
                die_errno(_("cannot open file '%s'"), sb.buf);
 
-       fprintf(submodule_dot_git, "gitdir: %s\n",
-               relative_path(sm_gitdir, path, &rel_path));
+       fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
+                      relative_path(sm_gitdir, path, &rel_path));
        if (fclose(submodule_dot_git))
                die(_("could not close file %s"), sb.buf);
        strbuf_reset(&sb);
        strbuf_reset(&rel_path);
 
-       cwd = xgetcwd();
        /* Redirect the worktree of the submodule in the superproject's config */
-       if (!is_absolute_path(sm_gitdir)) {
-               strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
-               free(sm_gitdir);
-               sm_gitdir = strbuf_detach(&sb, NULL);
-       }
-
-       strbuf_addf(&sb, "%s/%s", cwd, path);
        p = git_pathdup_submodule(path, "config");
        if (!p)
                die(_("could not get submodule directory for '%s'"), path);
        git_config_set_in_file(p, "core.worktree",
-                              relative_path(sb.buf, sm_gitdir, &rel_path));
+                              relative_path(path, sm_gitdir, &rel_path));
        strbuf_release(&sb);
        strbuf_release(&rel_path);
        free(sm_gitdir);
-       free(cwd);
+       free(path);
        free(p);
        return 0;
 }
index 506ac49..bbf4efa 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -435,12 +435,14 @@ int create_bundle(struct bundle_header *header, const char *path,
 
        /* write prerequisites */
        if (compute_and_write_prerequisites(bundle_fd, &revs, argc, argv))
-               return -1;
+               goto err;
 
        argc = setup_revisions(argc, argv, &revs, NULL);
 
-       if (argc > 1)
-               return error(_("unrecognized argument: %s"), argv[1]);
+       if (argc > 1) {
+               error(_("unrecognized argument: %s"), argv[1]);
+               goto err;
+       }
 
        object_array_remove_duplicates(&revs.pending);
 
@@ -448,17 +450,26 @@ int create_bundle(struct bundle_header *header, const char *path,
        if (!ref_count)
                die(_("Refusing to create empty bundle."));
        else if (ref_count < 0)
-               return -1;
+               goto err;
 
        /* write pack */
-       if (write_pack_data(bundle_fd, &revs))
-               return -1;
+       if (write_pack_data(bundle_fd, &revs)) {
+               bundle_fd = -1; /* already closed by the above call */
+               goto err;
+       }
 
        if (!bundle_to_stdout) {
                if (commit_lock_file(&lock))
                        die_errno(_("cannot create '%s'"), path);
        }
        return 0;
+err:
+       if (!bundle_to_stdout) {
+               if (0 <= bundle_fd)
+                       close(bundle_fd);
+               rollback_lock_file(&lock);
+       }
+       return -1;
 }
 
 int unbundle(struct bundle_header *header, int bundle_fd, int flags)
diff --git a/cache.h b/cache.h
index 9f09540..fd728f0 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -651,7 +651,6 @@ extern int prefer_symlink_refs;
 extern int log_all_ref_updates;
 extern int warn_ambiguous_refs;
 extern int warn_on_object_refname_ambiguity;
-extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern const char *apply_default_ignorewhitespace;
 extern const char *git_attributes_file;
@@ -664,6 +663,9 @@ extern size_t delta_base_cache_limit;
 extern unsigned long big_file_threshold;
 extern unsigned long pack_size_limit_cfg;
 
+void set_shared_repository(int value);
+int get_shared_repository(void);
+
 /*
  * Do replace refs need to be checked this run?  This variable is
  * initialized to true unless --no-replace-object is used or
@@ -745,9 +747,39 @@ extern int grafts_replace_parents;
  */
 #define GIT_REPO_VERSION 0
 #define GIT_REPO_VERSION_READ 1
-extern int repository_format_version;
 extern int repository_format_precious_objects;
-extern int check_repository_format(void);
+
+struct repository_format {
+       int version;
+       int precious_objects;
+       int is_bare;
+       char *work_tree;
+       struct string_list unknown_extensions;
+};
+
+/*
+ * Read the repository format characteristics from the config file "path" into
+ * "format" struct. Returns the numeric version. On error, -1 is returned,
+ * format->version is set to -1, and all other fields in the struct are
+ * undefined.
+ */
+int read_repository_format(struct repository_format *format, const char *path);
+
+/*
+ * Verify that the repository described by repository_format is something we
+ * can read. If it is, return 0. Otherwise, return -1, and "err" will describe
+ * any errors encountered.
+ */
+int verify_repository_format(const struct repository_format *format,
+                            struct strbuf *err);
+
+/*
+ * Check the repository format version in the path found in get_git_dir(),
+ * and die if it is a version we don't understand. Generally one would
+ * set_git_dir() before calling this, and use it only for "are we in a valid
+ * repo?".
+ */
+extern void check_repository_format(void);
 
 #define MTIME_CHANGED  0x0001
 #define CTIME_CHANGED  0x0002
@@ -926,8 +958,6 @@ static inline int is_empty_blob_sha1(const unsigned char *sha1)
 
 int git_mkstemp(char *path, size_t n, const char *template);
 
-int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
-
 /* set default permissions by passing mode arguments to open(2) */
 int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
 int git_mkstemp_mode(char *pattern, int mode);
@@ -1526,7 +1556,6 @@ extern void git_config(config_fn_t fn, void *);
 extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
                                   int respect_includes);
-extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_parse_maybe_bool(const char *);
 extern int git_config_int(const char *, const char *);
@@ -1550,7 +1579,6 @@ extern void git_config_set_multivar_in_file(const char *, const char *, const ch
 extern int git_config_rename_section(const char *, const char *);
 extern int git_config_rename_section_in_file(const char *, const char *, const char *);
 extern const char *git_etc_gitconfig(void);
-extern int check_repository_format_version(const char *var, const char *value, void *cb);
 extern int git_env_bool(const char *, int);
 extern unsigned long git_env_ulong(const char *, unsigned long);
 extern int git_config_system(void);
index d3fb264..11727f3 100644 (file)
@@ -3,12 +3,18 @@
 #define HEADER_HMAC_H
 #define HEADER_SHA_H
 #include <CommonCrypto/CommonHMAC.h>
-#define HMAC_CTX CCHmacContext
-#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len)
-#define HMAC_Update CCHmacUpdate
-#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash)
-#define HMAC_CTX_cleanup(ignore)
 #define EVP_md5(...) kCCHmacAlgMD5
+/* CCHmac doesn't take md_len and the return type is void */
+#define HMAC git_CC_HMAC
+static inline unsigned char *git_CC_HMAC(CCHmacAlgorithm alg,
+               const void *key, int key_len,
+               const unsigned char *data, size_t data_len,
+               unsigned char *md, unsigned int *md_len)
+{
+       CCHmac(alg, key, key_len, data, data_len, md);
+       return md;
+}
+
 #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
 #define APPLE_LION_OR_NEWER
 #include <Security/Security.h>
index 1de70ff..edec9e0 100644 (file)
@@ -142,6 +142,7 @@ static inline int fcntl(int fd, int cmd, ...)
 #define sigemptyset(x) (void)0
 static inline int sigaddset(sigset_t *set, int signum)
 { return 0; }
+#define SIG_BLOCK 0
 #define SIG_UNBLOCK 0
 static inline int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
 { return 0; }
index b6ed9e7..1c16408 100644 (file)
@@ -104,4 +104,11 @@ static inline void *pthread_getspecific(pthread_key_t key)
        return TlsGetValue(key);
 }
 
+#ifndef __MINGW64_VERSION_MAJOR
+static inline int pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
+{
+       return 0;
+}
+#endif
+
 #endif /* PTHREAD_H */
index 80a8c9a..519d51f 100644 (file)
@@ -2,37 +2,42 @@
 
 void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
 {
-       HANDLE hmap;
+       HANDLE osfhandle, hmap;
        void *temp;
-       off_t len;
-       struct stat st;
+       LARGE_INTEGER len;
        uint64_t o = offset;
        uint32_t l = o & 0xFFFFFFFF;
        uint32_t h = (o >> 32) & 0xFFFFFFFF;
 
-       if (!fstat(fd, &st))
-               len = st.st_size;
-       else
+       osfhandle = (HANDLE)_get_osfhandle(fd);
+       if (!GetFileSizeEx(osfhandle, &len))
                die("mmap: could not determine filesize");
 
-       if ((length + offset) > len)
-               length = xsize_t(len - offset);
+       if ((length + offset) > len.QuadPart)
+               length = xsize_t(len.QuadPart - offset);
 
        if (!(flags & MAP_PRIVATE))
                die("Invalid usage of mmap when built with USE_WIN32_MMAP");
 
-       hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), NULL,
-               PAGE_WRITECOPY, 0, 0, NULL);
+       hmap = CreateFileMapping(osfhandle, NULL,
+               prot == PROT_READ ? PAGE_READONLY : PAGE_WRITECOPY, 0, 0, NULL);
 
-       if (!hmap)
+       if (!hmap) {
+               errno = EINVAL;
                return MAP_FAILED;
+       }
 
-       temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
+       temp = MapViewOfFileEx(hmap, prot == PROT_READ ?
+                       FILE_MAP_READ : FILE_MAP_COPY, h, l, length, start);
 
        if (!CloseHandle(hmap))
                warning("unable to close file mapping handle");
 
-       return temp ? temp : MAP_FAILED;
+       if (temp)
+               return temp;
+
+       errno = GetLastError() == ERROR_COMMITMENT_LIMIT ? EFBIG : EINVAL;
+       return MAP_FAILED;
 }
 
 int git_munmap(void *start, size_t length)
index 9ba40bc..4c9b447 100644 (file)
--- a/config.c
+++ b/config.c
@@ -108,7 +108,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
 
        expanded = expand_user_path(path);
        if (!expanded)
-               return error("Could not expand include path '%s'", path);
+               return error("could not expand include path '%s'", path);
        path = expanded;
 
        /*
@@ -950,7 +950,7 @@ static int git_default_branch_config(const char *var, const char *value)
                else if (!strcmp(value, "always"))
                        autorebase = AUTOREBASE_ALWAYS;
                else
-                       return error("Malformed value for %s", var);
+                       return error("malformed value for %s", var);
                return 0;
        }
 
@@ -976,7 +976,7 @@ static int git_default_push_config(const char *var, const char *value)
                else if (!strcmp(value, "current"))
                        push_default = PUSH_DEFAULT_CURRENT;
                else {
-                       error("Malformed value for %s: %s", var, value);
+                       error("malformed value for %s: %s", var, value);
                        return error("Must be one of nothing, matching, simple, "
                                     "upstream or current.");
                }
@@ -1188,11 +1188,12 @@ int git_config_system(void)
        return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-int git_config_early(config_fn_t fn, void *data, const char *repo_config)
+static int do_git_config_sequence(config_fn_t fn, void *data)
 {
        int ret = 0, found = 0;
        char *xdg_config = xdg_config_home("config");
        char *user_config = expand_user_path("~/.gitconfig");
+       char *repo_config = git_pathdup("config");
 
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
@@ -1228,6 +1229,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 
        free(xdg_config);
        free(user_config);
+       free(repo_config);
        return ret == 0 ? found : ret;
 }
 
@@ -1235,8 +1237,6 @@ int git_config_with_options(config_fn_t fn, void *data,
                            struct git_config_source *config_source,
                            int respect_includes)
 {
-       char *repo_config = NULL;
-       int ret;
        struct config_include_data inc = CONFIG_INCLUDE_INIT;
 
        if (respect_includes) {
@@ -1257,11 +1257,7 @@ int git_config_with_options(config_fn_t fn, void *data,
        else if (config_source && config_source->blob)
                return git_config_from_blob_ref(fn, config_source->blob, data);
 
-       repo_config = git_pathdup("config");
-       ret = git_config_early(fn, data, repo_config);
-       if (repo_config)
-               free(repo_config);
-       return ret;
+       return do_git_config_sequence(fn, data);
 }
 
 static void git_config_raw(config_fn_t fn, void *data)
@@ -1313,14 +1309,11 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
        struct config_set_element k;
        struct config_set_element *found_entry;
        char *normalized_key;
-       int ret;
        /*
         * `key` may come from the user, so normalize it before using it
         * for querying entries from the hashmap.
         */
-       ret = git_config_parse_key(key, &normalized_key, NULL);
-
-       if (ret)
+       if (git_config_parse_key(key, &normalized_key, NULL))
                return NULL;
 
        hashmap_entry_init(&k, strhash(normalized_key));
@@ -2221,9 +2214,13 @@ void git_config_set_multivar_in_file(const char *config_filename,
                                     const char *key, const char *value,
                                     const char *value_regex, int multi_replace)
 {
-       if (git_config_set_multivar_in_file_gently(config_filename, key, value,
-                                                  value_regex, multi_replace) < 0)
-               die(_("Could not set '%s' to '%s'"), key, value);
+       if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
+                                                   value_regex, multi_replace))
+               return;
+       if (value)
+               die(_("could not set '%s' to '%s'"), key, value);
+       else
+               die(_("could not unset '%s'"), key);
 }
 
 int git_config_set_multivar_gently(const char *key, const char *value,
@@ -2404,7 +2401,7 @@ int git_config_rename_section(const char *old_name, const char *new_name)
 #undef config_error_nonbool
 int config_error_nonbool(const char *var)
 {
-       return error("Missing value for '%s'", var);
+       return error("missing value for '%s'", var);
 }
 
 int parse_config_key(const char *var,
index fe8096f..40d6b29 100644 (file)
@@ -187,6 +187,7 @@ ifeq ($(uname_O),Cygwin)
        X = .exe
        UNRELIABLE_FSTAT = UnfortunatelyYes
        SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield
+       OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
 endif
 ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
index 0cd9f46..c279025 100644 (file)
@@ -970,10 +970,6 @@ AC_CHECK_LIB([iconv], [locale_charset],
                      [CHARSET_LIB=-lcharset])])
 GIT_CONF_SUBST([CHARSET_LIB])
 #
-# Define NO_HMAC_CTX_CLEANUP=YesPlease if HMAC_CTX_cleanup is missing.
-AC_CHECK_LIB([crypto], [HMAC_CTX_cleanup],
-       [], [GIT_CONF_SUBST([NO_HMAC_CTX_CLEANUP], [YesPlease])])
-#
 # Define HAVE_CLOCK_GETTIME=YesPlease if clock_gettime is available.
 GIT_CHECK_FUNC(clock_gettime,
        [HAVE_CLOCK_GETTIME=YesPlease],
index bc77e66..53c71b4 100644 (file)
@@ -1,3 +1,38 @@
+Release 1.3.0
+=============
+
+* New options multimailhook.htmlInIntro and multimailhook.htmlInFooter
+  now allow using HTML in the introduction and footer of emails (e.g.
+  for a more pleasant formatting or to insert a link to the commit on
+  a web interface).
+
+* A new option multimailhook.commitBrowseURL gives a simpler (and less
+  flexible) way to add a link to a web interface for commit emails
+  than multimailhook.htmlInIntro and multimailhook.htmlInFooter.
+
+* A new public function config.add_config_parameters was added to
+  allow custom hooks to set specific Git configuration variables
+  without modifying the configuration files. See an example in
+  post-receive.example.
+
+* Error handling for SMTP has been improved (we used to print Python
+  backtraces for legitimate errors).
+
+* The SMTP mailer can now check TLS certificates when the newly added
+  configuration variable multimailhook.smtpCACerts.
+
+* Python 3 portability has been improved.
+
+* The documentation's formatting has been improved.
+
+* The testsuite has been improved (we now use pyflakes to check for
+  errors in the code).
+
+This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
+v1.7.10-406-gdc801e7, 2.1.4 and 2.8.1.339.g3ad15fd.
+
+No change since 1.3 RC1.
+
 Release 1.2.0
 =============
 
index 09efdb0..530ecbf 100644 (file)
@@ -1,3 +1,6 @@
+Contributing
+============
+
 git-multimail is an open-source project, built by volunteers. We would
 welcome your help!
 
@@ -6,9 +9,7 @@ and Matthieu Moy <matthieu.moy@grenoble-inp.fr>.
 
 Please note that although a copy of git-multimail is distributed in
 the "contrib" section of the main Git project, development takes place
-in a separate git-multimail repository on GitHub:
-
-    https://github.com/git-multimail/git-multimail
+in a separate `git-multimail repository on GitHub`_.
 
 Whenever enough changes to git-multimail have accumulated, a new
 code-drop of git-multimail will be submitted for inclusion in the Git
@@ -21,10 +22,12 @@ to the maintainers). Please sign off your patches as per the `Git
 project practice
 <https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
 
-General discussion of git-multimail can take place on the main Git
-mailing list,
-
-    git@vger.kernel.org
+General discussion of git-multimail can take place on the main `Git
+mailing list`_.
 
 Please CC emails regarding git-multimail to the maintainers so that we
 don't overlook them.
+
+
+.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
+.. _`Git mailing list`: git@vger.kernel.org
index 5512068..1e04801 100644 (file)
@@ -1,5 +1,5 @@
-git-multimail (version 1.2.0)
-=============================
+git-multimail 1.3.0
+===================
 
 .. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
     :target: https://travis-ci.org/git-multimail/git-multimail
@@ -127,6 +127,13 @@ changes of this type, please consider sharing them with the
 community.)
 
 
+Troubleshooting/FAQ
+-------------------
+
+Please read `<doc/troubleshooting.rst>`__ for frequently asked
+questions and common issues with git-multimail.
+
+
 Configuration
 -------------
 
@@ -134,19 +141,16 @@ By default, git-multimail mostly takes its configuration from the
 following ``git config`` settings:
 
 multimailhook.environment
-
     This describes the general environment of the repository. In most
     cases, you do not need to specify a value for this variable:
     `git-multimail` will autodetect which environment to use.
     Currently supported values:
 
-    * generic
-
+    generic
       the username of the pusher is read from $USER or $USERNAME and
       the repository name is derived from the repository's path.
 
-    * gitolite
-
+    gitolite
       the username of the pusher is read from $GL_USER, the repository
       name is read from $GL_REPO, and the From: header value is
       optionally read from gitolite.conf (see multimailhook.from).
@@ -154,8 +158,7 @@ multimailhook.environment
       For more information about gitolite and git-multimail, read
       `<doc/gitolite.rst>`__
 
-    * stash
-
+    stash
       Environment to use when ``git-multimail`` is ran as an Atlassian
       BitBucket Server (formerly known as Atlassian Stash) hook.
 
@@ -169,8 +172,7 @@ multimailhook.environment
       and repo come from these two command line flags, which must be
       specified.
 
-    * gerrit
-
+    gerrit
       Environment to use when ``git-multimail`` is ran as a
       ``ref-updated`` Gerrit hook.
 
@@ -205,14 +207,12 @@ multimailhook.environment
     * If none of the above apply, then ``generic`` is used.
 
 multimailhook.repoName
-
     A short name of this Git repository, to be used in various places
     in the notification email text.  The default is to use $GL_REPO
     for gitolite repositories, or otherwise to derive this value from
     the repository path name.
 
 multimailhook.mailingList
-
     The list of email addresses to which notification emails should be
     sent, as RFC 2822 email addresses separated by commas.  This
     configuration option can be multivalued.  Leave it unset or set it
@@ -221,7 +221,6 @@ multimailhook.mailingList
     specific types of notification email.
 
 multimailhook.refchangeList
-
     The list of email addresses to which summary emails about
     reference changes should be sent, as RFC 2822 email addresses
     separated by commas.  This configuration option can be
@@ -231,7 +230,6 @@ multimailhook.refchangeList
     multimailhook.mailingList is set.
 
 multimailhook.announceList
-
     The list of email addresses to which emails about new annotated
     tags should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
@@ -241,7 +239,6 @@ multimailhook.announceList
     even if one of the other values is set.
 
 multimailhook.commitList
-
     The list of email addresses to which emails about individual new
     commits should be sent, as RFC 2822 email addresses separated by
     commas.  This configuration option can be multivalued.  The
@@ -251,7 +248,6 @@ multimailhook.commitList
     multimailhook.mailingList is set.
 
 multimailhook.announceShortlog
-
     If this option is set to true, then emails about changes to
     annotated tags include a shortlog of changes since the previous
     tag.  This can be useful if the annotated tags represent releases;
@@ -261,7 +257,6 @@ multimailhook.announceShortlog
     rather than useful.  Default is false.
 
 multimailhook.commitEmailFormat
-
     The format of email messages for the individual commits, can be "text" or
     "html". In the latter case, the emails will include diffs using colorized
     HTML instead of plain text used by default. Note that this  currently the
@@ -274,8 +269,43 @@ multimailhook.commitEmailFormat
     the message starting with ``+++`` or ``---`` colored in red or
     green).
 
-multimailhook.refchangeShowGraph
+    By default, all the message is HTML-escaped. See
+    ``multimailhook.htmlInIntro`` to change this behavior.
+
+multimailhook.commitBrowseURL
+    Used to generate a link to an online repository browser in commit
+    emails. This variable must be a string. Format directives like
+    ``%(<variable>)s`` will be expanded the same way as template
+    strings. In particular, ``%(id)s`` will be replaced by the full
+    Git commit identifier (40-chars hexadecimal).
+
+    If the string does not contain any format directive, then
+    ``%(id)s`` will be automatically added to the string. If you don't
+    want ``%(id)s`` to be automatically added, use the empty format
+    directive ``%()s`` anywhere in the string.
+
+    For example, a suitable value for the git-multimail project itself
+    would be
+    ``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
+
+multimailhook.htmlInIntro, multimailhook.htmlInFooter
+    When generating an HTML message, git-multimail escapes any HTML
+    sequence by default. This means that if a template contains HTML
+    like ``<a href="foo">link</a>``, the reader will see the HTML
+    source code and not a proper link.
+
+    Set ``multimailhook.htmlInIntro`` to true to allow writting HTML
+    formatting in introduction templates. Similarly, set
+    ``multimailhook.htmlInFooter`` for HTML in the footer.
 
+    Variables expanded in the template are still escaped. For example,
+    if a repository's path contains a ``<``, it will be rendered as
+    such in the message.
+
+    Read `<doc/customizing-emails.rst>`__ for more details and
+    examples.
+
+multimailhook.refchangeShowGraph
     If this option is set to true, then summary emails about reference
     changes will additionally include:
 
@@ -287,7 +317,6 @@ multimailhook.refchangeShowGraph
     specified in graphOpts.  The default is false.
 
 multimailhook.refchangeShowLog
-
     If this option is set to true, then summary emails about reference
     changes will include a detailed log of the added commits in
     addition to the one line summary.  The log is generated by running
@@ -295,71 +324,80 @@ multimailhook.refchangeShowLog
     Default is false.
 
 multimailhook.mailer
-
     This option changes the way emails are sent.  Accepted values are:
 
-    - sendmail (the default): use the command ``/usr/sbin/sendmail`` or
+    * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
       ``/usr/lib/sendmail`` (or sendmailCommand, if configured).  This
       mode can be further customized via the following options:
 
-      * multimailhook.sendmailCommand
-
-        The command used by mailer ``sendmail`` to send emails.  Shell
-        quoting is allowed in the value of this setting, but remember that
-        Git requires double-quotes to be escaped; e.g.::
+      multimailhook.sendmailCommand
+          The command used by mailer ``sendmail`` to send emails.  Shell
+          quoting is allowed in the value of this setting, but remember that
+          Git requires double-quotes to be escaped; e.g.::
 
-             git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
+              git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
 
-        Default is '/usr/sbin/sendmail -oi -t' or
-        '/usr/lib/sendmail -oi -t' (depending on which file is
-        present and executable).
+          Default is '/usr/sbin/sendmail -oi -t' or
+          '/usr/lib/sendmail -oi -t' (depending on which file is
+          present and executable).
 
-      * multimailhook.envelopeSender
+      multimailhook.envelopeSender
+          If set then pass this value to sendmail via the -f option to set
+          the envelope sender address.
 
-        If set then pass this value to sendmail via the -f option to set
-        the envelope sender address.
-
-    - smtp: use Python's smtplib.  This is useful when the sendmail
+    * **smtp**: use Python's smtplib.  This is useful when the sendmail
       command is not available on the system.  This mode can be
       further customized via the following options:
 
-      * multimailhook.smtpServer
-
-        The name of the SMTP server to connect to.  The value can
-        also include a colon and a port number; e.g.,
-        ``mail.example.com:25``.  Default is 'localhost' using port 25.
-
-      * multimailhook.smtpUser
-      * multimailhook.smtpPass
-
-        Server username and password. Required if smtpEncryption is 'ssl'.
-        Note that the username and password currently need to be
-        set cleartext in the configuration file, which is not
-        recommended. If you need to use this option, be sure your
-        configuration file is read-only.
+      multimailhook.smtpServer
+          The name of the SMTP server to connect to.  The value can
+          also include a colon and a port number; e.g.,
+          ``mail.example.com:25``.  Default is 'localhost' using port 25.
 
-      * multimailhook.envelopeSender
+      multimailhook.smtpUser, multimailhook.smtpPass
+          Server username and password. Required if smtpEncryption is 'ssl'.
+          Note that the username and password currently need to be
+          set cleartext in the configuration file, which is not
+          recommended. If you need to use this option, be sure your
+          configuration file is read-only.
 
+      multimailhook.envelopeSender
         The sender address to be passed to the SMTP server.  If
         unset, then the value of multimailhook.from is used.
 
-      * multimailhook.smtpServerTimeout
-
+      multimailhook.smtpServerTimeout
         Timeout in seconds.
 
-      * multimailhook.smtpEncryption
-
-        Set the security type. Allowed values: none, ssl, tls.
-        Default=none.
-
-      * multimailhook.smtpServerDebugLevel
-
+      multimailhook.smtpEncryption
+        Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
+        Default is ``none``.
+
+      multimailhook.smtpCACerts
+        Set the path to a list of trusted CA certificate to verify the
+        server certificate, only supported when ``smtpEncryption`` is
+        ``tls``. If unset or empty, the server certificate is not
+        verified. If it targets a file containing a list of trusted CA
+        certificates (PEM format) these CAs will be used to verify the
+        server certificate. For debian, you can set
+        ``/etc/ssl/certs/ca-certificates.crt`` for using the system
+        trusted CAs. For self-signed server, you can add your server
+        certificate to the system store::
+
+            cd /usr/local/share/ca-certificates/
+            openssl s_client -starttls smtp \
+                   -connect mail.example.net:587 -showcerts \
+                   </dev/null 2>/dev/null \
+                 | openssl x509 -outform PEM >mail.example.net.crt
+            update-ca-certificates
+
+        and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
+        directly use your ``/path/to/mail.example.net.crt``. Default is
+        unset.
+
+      multimailhook.smtpServerDebugLevel
         Integer number. Set to greater than 0 to activate debugging.
 
-multimailhook.from
-multimailhook.fromCommit
-multimailhook.fromRefchange
-
+multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
     If set, use this value in the From: field of generated emails.
     ``fromCommit`` is used for commit emails, ``fromRefchange`` is
     used for refchange emails, and ``from`` is used as fall-back in
@@ -372,7 +410,7 @@ multimailhook.fromRefchange
     - The value ``pusher``, in which case the pusher's address (if
       available) will be used.
 
-    - The value ``author`` (meaningful only for replyToCommit), in which
+    - The value ``author`` (meaningful only for ``fromCommit``), in which
       case the commit author's address will be used.
 
     If config values are unset, the value of the From: header is
@@ -396,14 +434,12 @@ multimailhook.fromRefchange
     3. Use the value of multimailhook.envelopeSender.
 
 multimailhook.administrator
-
     The name and/or email address of the administrator of the Git
     repository; used in FOOTER_TEMPLATE.  Default is
     multimailhook.envelopesender if it is set; otherwise a generic
     string is used.
 
 multimailhook.emailPrefix
-
     All emails have this string prepended to their subjects, to aid
     email filtering (though filtering based on the X-Git-* email
     headers is probably more robust).  Default is the short name of
@@ -411,16 +447,14 @@ multimailhook.emailPrefix
     value to the empty string to suppress the email prefix.
 
 multimailhook.emailMaxLines
-
     The maximum number of lines that should be included in the body of
     a generated email.  If not specified, there is no limit.  Lines
     beyond the limit are suppressed and counted, and a final line is
     added indicating the number of suppressed lines.
 
 multimailhook.emailMaxLineLength
-
     The maximum length of a line in the email body.  Lines longer than
-    this limit are truncated to this length with a trailing `` [...]``
+    this limit are truncated to this length with a trailing ``[...]``
     added to indicate the missing text.  The default is 500, because
     (a) diffs with longer lines are probably from binary files, for
     which a diff is useless, and (b) even if a text file has such long
@@ -428,7 +462,6 @@ multimailhook.emailMaxLineLength
     truncation, set this option to 0.
 
 multimailhook.maxCommitEmails
-
     The maximum number of commit emails to send for a given change.
     When the number of patches is larger that this value, only the
     summary refchange email is sent.  This can avoid accidental
@@ -436,14 +469,12 @@ multimailhook.maxCommitEmails
     emails limit, set this option to 0.  The default is 500.
 
 multimailhook.emailStrictUTF8
-
     If this boolean option is set to `true`, then the main part of the
     email body is forced to be valid UTF-8.  Any characters that are
     not valid UTF-8 are converted to the Unicode replacement
     character, U+FFFD.  The default is `true`.
 
 multimailhook.diffOpts
-
     Options passed to ``git diff-tree`` when generating the summary
     information for ReferenceChange emails.  Default is ``--stat
     --summary --find-copies-harder``.  Add -p to those options to
@@ -452,7 +483,6 @@ multimailhook.diffOpts
     details.
 
 multimailhook.graphOpts
-
     Options passed to ``git log --graph`` when generating graphs for the
     reference change summary emails (used only if refchangeShowGraph
     is true).  The default is '--oneline --decorate'.
@@ -460,7 +490,6 @@ multimailhook.graphOpts
     Shell quoting is allowed; see logOpts for details.
 
 multimailhook.logOpts
-
     Options passed to ``git log`` to generate additional info for
     reference change emails (used only if refchangeShowLog is set).
     For example, adding -p will show each commit's complete diff.  The
@@ -479,7 +508,6 @@ multimailhook.logOpts
               logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
 
 multimailhook.commitLogOpts
-
     Options passed to ``git log`` to generate additional info for
     revision change emails.  For example, adding --ignore-all-spaces
     will suppress whitespace changes.  The default options are ``-C
@@ -487,26 +515,21 @@ multimailhook.commitLogOpts
     multimailhook.logOpts for details.
 
 multimailhook.dateSubstitute
-
     String to use as a substitute for ``Date:`` in the output of ``git
     log`` while formatting commit messages. This is usefull to avoid
     emitting a line that can be interpreted by mailers as the start of
     a cited message (Zimbra webmail in particular). Defaults to
-    ``CommitDate: ``. Set to an empty string or ``none`` to deactivate
+    ``CommitDate:``. Set to an empty string or ``none`` to deactivate
     the behavior.
 
 multimailhook.emailDomain
-
     Domain name appended to the username of the person doing the push
     to convert it into an email address
     (via ``"%s@%s" % (username, emaildomain)``). More complicated
     schemes can be implemented by overriding Environment and
     overriding its get_pusher_email() method.
 
-multimailhook.replyTo
-multimailhook.replyToCommit
-multimailhook.replyToRefchange
-
+multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
     Addresses to use in the Reply-To: field for commit emails
     (replyToCommit) and refchange emails (replyToRefchange).
     multimailhook.replyTo is used as default when replyToCommit or
@@ -519,32 +542,24 @@ multimailhook.replyToRefchange
     commit emails.
 
 multimailhook.quiet
-
     Do not output the list of email recipients from the hook
 
 multimailhook.stdout
-
     For debugging, send emails to stdout rather than to the
     mailer.  Equivalent to the --stdout command line option
 
 multimailhook.scanCommitForCc
-
     If this option is set to true, than recipients from lines in commit body
     that starts with ``CC:`` will be added to CC list.
     Default: false
 
 multimailhook.combineWhenSingleCommit
-
     If this option is set to true and a single new commit is pushed to
     a branch, combine the summary and commit email messages into a
     single email.
     Default: true
 
-multimailhook.refFilterInclusionRegex
-multimailhook.refFilterExclusionRegex
-multimailhook.refFilterDoSendRegex
-multimailhook.refFilterDontSendRegex
-
+multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
     **Warning:** these options are experimental. They should work, but
     the user-interface is not stable yet (in particular, the option
     names may change). If you want to participate in stabilizing the
@@ -626,14 +641,16 @@ git-multimail is mostly customized via an "environment" that describes
 the local environment in which Git is running.  Two types of
 environment are built in:
 
-* GenericEnvironment: a stand-alone Git repository.
+GenericEnvironment
+    a stand-alone Git repository.
 
-* GitoliteEnvironment: a Git repository that is managed by gitolite
-  [3]_.  For such repositories, the identity of the pusher is read from
-  environment variable $GL_USER, the name of the repository is read
-  from $GL_REPO (if it is not overridden by multimailhook.reponame),
-  and the From: header value is optionally read from gitolite.conf
-  (see multimailhook.from).
+GitoliteEnvironment
+    a Git repository that is managed by gitolite
+    [3]_.  For such repositories, the identity of the pusher is read from
+    environment variable $GL_USER, the name of the repository is read
+    from $GL_REPO (if it is not overridden by multimailhook.reponame),
+    and the From: header value is optionally read from gitolite.conf
+    (see multimailhook.from).
 
 By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
 $GL_REPO are set, and otherwise assumes GenericEnvironment.
index 300a2a4..ee1fa75 100644 (file)
@@ -6,10 +6,10 @@ website:
     https://github.com/git-multimail/git-multimail
 
 The version in this directory was obtained from the upstream project
-on October 11 2015 and consists of the "git-multimail" subdirectory from
+on May 03 2016 and consists of the "git-multimail" subdirectory from
 revision
 
-    c0791b9ef5821a746fc3475c25765e640452eaae refs/tags/1.2.0
+    26f3ae9f86aa7f8a054ba89235c4d3879f98b03d refs/tags/1.3.0
 
 Please see the README file in this directory for information about how
 to report bugs or contribute to git-multimail.
diff --git a/contrib/hooks/multimail/doc/customizing-emails.rst b/contrib/hooks/multimail/doc/customizing-emails.rst
new file mode 100644 (file)
index 0000000..3f5b67f
--- /dev/null
@@ -0,0 +1,56 @@
+Customizing the content and formatting of emails
+================================================
+
+Overloading template strings
+----------------------------
+
+The content of emails is generated based on template strings defined
+in ``git_multimail.py``. You can customize these template strings
+without changing the script itself, by defining a Python wrapper
+around it. The python wrapper should ``import git_multimail`` and then
+override the ``git_multimail.*`` strings like this::
+
+  import sys  # needed for sys.argv
+
+  # Import and customize git_multimail:
+  import git_multimail
+  git_multimail.REVISION_INTRO_TEMPLATE = """..."""
+  git_multimail.COMBINED_INTRO_TEMPLATE = git_multimail.REVISION_INTRO_TEMPLATE
+
+  # start git_multimail itself:
+  git_multimail.main(sys.argv[1:])
+
+The template strings can use any value already used in the existing
+templates (read the source code).
+
+Using HTML in template strings
+------------------------------
+
+If ``multimailhook.commitEmailFormat`` is set to HTML, then
+git-multimail will generate HTML emails for commit notifications. The
+log and diff will be formatted automatically by git-multimail. By
+default, any HTML special character in the templates will be escaped.
+
+To use HTML formatting in the introduction of the email, set
+``multimailhook.htmlInIntro`` to ``true``. Then, the template can
+contain any HTML tags, that will be sent as-is in the email. For
+example, to add some formatting and a link to the online commit, use
+a format like::
+
+  git_multimail.REVISION_INTRO_TEMPLATE = """\
+  <span style="color:#808080">This is an automated email from the git hooks/post-receive script.</span><br /><br />
+
+  <strong>%(pusher)s</strong> pushed a commit to %(refname_type)s %(short_refname)s
+  in repository %(repo_shortname)s.<br />
+
+  <a href="https://github.com/git-multimail/git-multimail/commit/%(newrev)s">View on GitHub</a>.
+  """
+
+Note that the values expanded from ``%(variable)s`` in the format
+strings will still be escaped.
+
+For a less flexible but easier to set up way to add a link to commit
+emails, see ``multimailhook.commitBrowseURL``.
+
+Similarly, one can set ``multimailhook.htmlInFooter`` and override any
+of the ``*_FOOTER*`` template strings.
diff --git a/contrib/hooks/multimail/doc/troubleshooting.rst b/contrib/hooks/multimail/doc/troubleshooting.rst
new file mode 100644 (file)
index 0000000..d3f346f
--- /dev/null
@@ -0,0 +1,44 @@
+Troubleshooting issues with git-multimail: a FAQ
+================================================
+
+Git is not using the right address in the From/To/Reply-To field
+----------------------------------------------------------------
+
+First, make sure that git-multimail actually uses what you think it is
+using. A lot happens to your email (especially when posting to a
+mailing-list) between the time `git_multimail.py` sends it and the
+time it reaches your inbox.
+
+A simple test (to do on a test repository, do not use in production as
+it would disable email sending): change your post-receive hook to call
+`git_multimail.py` with the `--stdout` option, and try to push to the
+repository. You should see something like::
+
+  Counting objects: 3, done.
+  Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
+  Total 3 (delta 0), reused 0 (delta 0)
+  remote: Sending notification emails to: foo.bar@example.com
+  remote: ===========================================================================
+  remote: Date: Mon, 25 Apr 2016 18:39:59 +0200
+  remote: To: foo.bar@example.com
+  remote: Subject: [git] branch master updated: foo
+  remote: MIME-Version: 1.0
+  remote: Content-Type: text/plain; charset=utf-8
+  remote: Content-Transfer-Encoding: 8bit
+  remote: Message-ID: <20160425163959.2311.20498@anie>
+  remote: From: Auth Or <Foo.Bar@example.com>
+  remote: Reply-To: Auth Or <Foo.Bar@example.com>
+  remote: X-Git-Host: example
+  ...
+  remote: --
+  remote: To stop receiving notification emails like this one, please contact
+  remote: the administrator of this repository.
+  remote: ===========================================================================
+  To /path/to/repo
+     6278f04..e173f20  master -> master
+
+Note: this does not include the sender (Return-Path: header), as it is
+not part of the message content but passed to the mailer. Some mailer
+show the ``Sender:`` field instead of the ``From:`` field (for
+example, Zimbra Webmail shows ``From: <sender-field> on behalf of
+<from-field>``).
index 0180dba..f2c92ae 100755 (executable)
@@ -1,6 +1,6 @@
 #! /usr/bin/env python
 
-__version__ = '1.2.0'
+__version__ = '1.3.0'
 
 # Copyright (c) 2015 Matthieu Moy and others
 # Copyright (c) 2012-2014 Michael Haggerty and others
@@ -57,6 +57,11 @@ import subprocess
 import shlex
 import optparse
 import smtplib
+try:
+    import ssl
+except ImportError:
+    # Python < 2.6 do not have ssl, but that's OK if we don't use it.
+    pass
 import time
 import cgi
 
@@ -75,6 +80,9 @@ def is_ascii(s):
 
 
 if PYTHON3:
+    def is_string(s):
+        return isinstance(s, str)
+
     def str_to_bytes(s):
         return s.encode(ENCODING)
 
@@ -91,6 +99,12 @@ if PYTHON3:
         except UnicodeEncodeError:
             f.buffer.write(msg.encode(ENCODING))
 else:
+    def is_string(s):
+        try:
+            return isinstance(s, basestring)
+        except NameError:  # Silence Pyflakes warning
+            raise
+
     def str_to_bytes(s):
         return s
 
@@ -313,6 +327,16 @@ in repository %(repo_shortname)s.
 
 """
 
+LINK_TEXT_TEMPLATE = """\
+View the commit online:
+%(browse_url)s
+
+"""
+
+LINK_HTML_TEMPLATE = """\
+<p><a href="%(browse_url)s">View the commit online</a>.</p>
+"""
+
 
 REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
 
@@ -532,6 +556,28 @@ class Config(object):
         assert words[-1] == ''
         return words[:-1]
 
+    @staticmethod
+    def add_config_parameters(c):
+        """Add configuration parameters to Git.
+
+        c is either an str or a list of str, each element being of the
+        form 'var=val' or 'var', with the same syntax and meaning as
+        the argument of 'git -c var=val'.
+        """
+        if isinstance(c, str):
+            c = (c,)
+        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+        if parameters:
+            parameters += ' '
+        # git expects GIT_CONFIG_PARAMETERS to be of the form
+        #    "'name1=value1' 'name2=value2' 'name3=value3'"
+        # including everything inside the double quotes (but not the double
+        # quotes themselves).  Spacing is critical.  Also, if a value contains
+        # a literal single quote that quote must be represented using the
+        # four character sequence: '\''
+        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c)
+        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+
     def get(self, name, default=None):
         try:
             values = self._split(read_git_output(
@@ -745,6 +791,12 @@ class Change(object):
         values['multimail_version'] = get_version()
         return values
 
+    # Aliases usable in template strings. Tuple of pairs (destination,
+    # source).
+    VALUES_ALIAS = (
+        ("id", "newrev"),
+        )
+
     def get_values(self, **extra_values):
         """Return a dictionary {keyword: expansion} for this Change.
 
@@ -760,6 +812,9 @@ class Change(object):
         values = self._values.copy()
         if extra_values:
             values.update(extra_values)
+
+        for alias, val in self.VALUES_ALIAS:
+            values[alias] = values[val]
         return values
 
     def expand(self, template, **extra_values):
@@ -772,10 +827,14 @@ class Change(object):
 
         return template % self.get_values(**extra_values)
 
-    def expand_lines(self, template, **extra_values):
+    def expand_lines(self, template, html_escape_val=False, **extra_values):
         """Break template into lines and expand each line."""
 
         values = self.get_values(**extra_values)
+        if html_escape_val:
+            for k in values:
+                if is_string(values[k]):
+                    values[k] = cgi.escape(values[k], True)
         for line in template.splitlines(True):
             yield line % values
 
@@ -787,9 +846,10 @@ class Change(object):
 
         values = self.get_values(**extra_values)
         if self._contains_html_diff:
-            values['contenttype'] = 'html'
+            self._content_type = 'html'
         else:
-            values['contenttype'] = 'plain'
+            self._content_type = 'plain'
+        values['contenttype'] = self._content_type
 
         for line in template.splitlines():
             (name, value) = line.split(': ', 1)
@@ -819,7 +879,11 @@ class Change(object):
 
         raise NotImplementedError()
 
-    def generate_email_intro(self):
+    def generate_browse_link(self, base_url):
+        """Generate a link to an online repository browser."""
+        return iter(())
+
+    def generate_email_intro(self, html_escape_val=False):
         """Generate the email intro for this Change, a line at a time.
 
         The output will be used as the standard boilerplate at the top
@@ -835,7 +899,7 @@ class Change(object):
 
         raise NotImplementedError()
 
-    def generate_email_footer(self):
+    def generate_email_footer(self, html_escape_val):
         """Generate the footer of the email, a line at a time.
 
         The footer is always included, irrespective of
@@ -876,9 +940,18 @@ class Change(object):
         for line in self.generate_email_header(**extra_header_values):
             yield line
         yield '\n'
-        for line in self._wrap_for_html(self.generate_email_intro()):
+        html_escape_val = (self.environment.html_in_intro and
+                           self._contains_html_diff)
+        intro = self.generate_email_intro(html_escape_val)
+        if not self.environment.html_in_intro:
+            intro = self._wrap_for_html(intro)
+        for line in intro:
             yield line
 
+        if self.environment.commitBrowseURL:
+            for line in self.generate_browse_link(self.environment.commitBrowseURL):
+                yield line
+
         body = self.generate_email_body(push)
         if body_filter is not None:
             body = body_filter(body)
@@ -939,8 +1012,12 @@ class Change(object):
             yield line
         if self._contains_html_diff:
             yield '</pre>'
-
-        for line in self._wrap_for_html(self.generate_email_footer()):
+        html_escape_val = (self.environment.html_in_footer and
+                           self._contains_html_diff)
+        footer = self.generate_email_footer(html_escape_val)
+        if not self.environment.html_in_footer:
+            footer = self._wrap_for_html(footer)
+        for line in footer:
             yield line
 
     def get_alt_fromaddr(self):
@@ -992,6 +1069,7 @@ class Revision(Change):
         values['rev_short'] = self.rev.short
         values['change_type'] = self.change_type
         values['refname'] = self.refname
+        values['newrev'] = self.rev.sha1
         values['short_refname'] = self.reference_change.short_refname
         values['refname_type'] = self.reference_change.refname_type
         values['reply_to_msgid'] = self.reference_change.msgid
@@ -1015,8 +1093,26 @@ class Revision(Change):
                 ):
             yield line
 
-    def generate_email_intro(self):
-        for line in self.expand_lines(REVISION_INTRO_TEMPLATE):
+    def generate_browse_link(self, base_url):
+        if '%(' not in base_url:
+            base_url += '%(id)s'
+        url = "".join(self.expand_lines(base_url))
+        if self._content_type == 'html':
+            for line in self.expand_lines(LINK_HTML_TEMPLATE,
+                                          html_escape_val=True,
+                                          browse_url=url):
+                yield line
+        elif self._content_type == 'plain':
+            for line in self.expand_lines(LINK_TEXT_TEMPLATE,
+                                          html_escape_val=False,
+                                          browse_url=url):
+                yield line
+        else:
+            raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.")
+
+    def generate_email_intro(self, html_escape_val=False):
+        for line in self.expand_lines(REVISION_INTRO_TEMPLATE,
+                                      html_escape_val=html_escape_val):
             yield line
 
     def generate_email_body(self, push):
@@ -1031,8 +1127,9 @@ class Revision(Change):
             else:
                 yield line
 
-    def generate_email_footer(self):
-        return self.expand_lines(REVISION_FOOTER_TEMPLATE)
+    def generate_email_footer(self, html_escape_val):
+        return self.expand_lines(REVISION_FOOTER_TEMPLATE,
+                                 html_escape_val=html_escape_val)
 
     def generate_email(self, push, body_filter=None, extra_header_values={}):
         self._contains_diff()
@@ -1217,8 +1314,9 @@ class ReferenceChange(Change):
                 ):
             yield line
 
-    def generate_email_intro(self):
-        for line in self.expand_lines(self.intro_template):
+    def generate_email_intro(self, html_escape_val=False):
+        for line in self.expand_lines(self.intro_template,
+                                      html_escape_val=html_escape_val):
             yield line
 
     def generate_email_body(self, push):
@@ -1238,8 +1336,9 @@ class ReferenceChange(Change):
         for line in self.generate_revision_change_summary(push):
             yield line
 
-    def generate_email_footer(self):
-        return self.expand_lines(self.footer_template)
+    def generate_email_footer(self, html_escape_val):
+        return self.expand_lines(self.footer_template,
+                                 html_escape_val=html_escape_val)
 
     def generate_revision_change_graph(self, push):
         if self.showgraph:
@@ -1896,6 +1995,7 @@ class SMTPMailer(Mailer):
                  smtpservertimeout=10.0, smtpserverdebuglevel=0,
                  smtpencryption='none',
                  smtpuser='', smtppass='',
+                 smtpcacerts=''
                  ):
         if not envelopesender:
             sys.stderr.write(
@@ -1915,6 +2015,7 @@ class SMTPMailer(Mailer):
         self.security = smtpencryption
         self.username = smtpuser
         self.password = smtppass
+        self.smtpcacerts = smtpcacerts
         try:
             def call(klass, server, timeout):
                 try:
@@ -1925,13 +2026,56 @@ class SMTPMailer(Mailer):
             if self.security == 'none':
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'ssl':
+                if self.smtpcacerts:
+                    raise smtplib.SMTPException(
+                        "Checking certificate is not supported for ssl, prefer starttls"
+                        )
                 self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'tls':
+                if 'ssl' not in sys.modules:
+                    sys.stderr.write(
+                        '*** Your Python version does not have the ssl library installed\n'
+                        '*** smtpEncryption=tls is not available.\n'
+                        '*** Either upgrade Python to 2.6 or later\n'
+                        '    or use git_multimail.py version 1.2.\n')
                 if ':' not in self.smtpserver:
                     self.smtpserver += ':587'  # default port for TLS
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+                # start: ehlo + starttls
+                # equivalent to
+                #     self.smtp.ehlo()
+                #     self.smtp.starttls()
+                # with acces to the ssl layer
                 self.smtp.ehlo()
-                self.smtp.starttls()
+                if not self.smtp.has_extn("starttls"):
+                    raise smtplib.SMTPException("STARTTLS extension not supported by server")
+                resp, reply = self.smtp.docmd("STARTTLS")
+                if resp != 220:
+                    raise smtplib.SMTPException("Wrong answer to the STARTTLS command")
+                if self.smtpcacerts:
+                    self.smtp.sock = ssl.wrap_socket(
+                        self.smtp.sock,
+                        ca_certs=self.smtpcacerts,
+                        cert_reqs=ssl.CERT_REQUIRED
+                        )
+                else:
+                    self.smtp.sock = ssl.wrap_socket(
+                        self.smtp.sock,
+                        cert_reqs=ssl.CERT_NONE
+                        )
+                    sys.stderr.write(
+                        '*** Warning, the server certificat is not verified (smtp) ***\n'
+                        '***          set the option smtpCACerts                   ***\n'
+                        )
+                if not hasattr(self.smtp.sock, "read"):
+                    # using httplib.FakeSocket with Python 2.5.x or earlier
+                    self.smtp.sock.read = self.smtp.sock.recv
+                self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock)
+                self.smtp.helo_resp = None
+                self.smtp.ehlo_resp = None
+                self.smtp.esmtp_features = {}
+                self.smtp.does_esmtp = 0
+                # end:   ehlo + starttls
                 self.smtp.ehlo()
             else:
                 sys.stdout.write('*** Error: Control reached an invalid option. ***')
@@ -1951,6 +2095,7 @@ class SMTPMailer(Mailer):
     def __del__(self):
         if hasattr(self, 'smtp'):
             self.smtp.quit()
+            del self.smtp
 
     def send(self, lines, to_addrs):
         try:
@@ -1958,13 +2103,24 @@ class SMTPMailer(Mailer):
                 self.smtp.login(self.username, self.password)
             msg = ''.join(lines)
             # turn comma-separated list into Python list if needed.
-            if isinstance(to_addrs, basestring):
+            if is_string(to_addrs):
                 to_addrs = [email for (name, email) in getaddresses([to_addrs])]
             self.smtp.sendmail(self.envelopesender, to_addrs, msg)
-        except Exception:
+        except smtplib.SMTPResponseException:
             sys.stderr.write('*** Error sending email ***\n')
-            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
-            self.smtp.quit()
+            err = sys.exc_info()[1]
+            sys.stderr.write('*** Error %d: %s\n' % (err.smtp_code,
+                                                     bytes_to_str(err.smtp_error)))
+            try:
+                smtp = self.smtp
+                # delete the field before quit() so that in case of
+                # error, self.smtp is deleted anyway.
+                del self.smtp
+                smtp.quit()
+            except:
+                sys.stderr.write('*** Error closing the SMTP connection ***\n')
+                sys.stderr.write('*** Exiting anyway ... ***\n')
+                sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             sys.exit(1)
 
 
@@ -2097,6 +2253,14 @@ class Environment(object):
             If "html", generate commit emails in HTML instead of plain text
             used by default.
 
+        html_in_intro (bool)
+        html_in_footer (bool)
+
+            When generating HTML emails, the introduction (respectively,
+            the footer) will be HTML-escaped iff html_in_intro (respectively,
+            the footer) is true. When false, only the values used to expand
+            the template are escaped.
+
         refchange_showgraph (bool)
 
             True iff refchanges emails should include a detailed graph.
@@ -2160,6 +2324,9 @@ class Environment(object):
         self.osenv = osenv or os.environ
         self.announce_show_shortlog = False
         self.commit_email_format = "text"
+        self.html_in_intro = False
+        self.html_in_footer = False
+        self.commitBrowseURL = None
         self.maxcommitemails = 500
         self.diffopts = ['--stat', '--summary', '--find-copies-harder']
         self.graphopts = ['--oneline', '--decorate']
@@ -2236,7 +2403,7 @@ class Environment(object):
         The return value is always a new dictionary."""
 
         if self._values is None:
-            values = {}
+            values = {'': ''}  # %()s expands to the empty string.
 
             for key in self.COMPUTED_KEYS:
                 value = getattr(self, 'get_%s' % (key,))()
@@ -2375,6 +2542,16 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
             else:
                 self.commit_email_format = commit_email_format
 
+        html_in_intro = config.get_bool('htmlInIntro')
+        if html_in_intro is not None:
+            self.html_in_intro = html_in_intro
+
+        html_in_footer = config.get_bool('htmlInFooter')
+        if html_in_footer is not None:
+            self.html_in_footer = html_in_footer
+
+        self.commitBrowseURL = config.get('commitBrowseURL')
+
         maxcommitemails = config.get('maxcommitemails')
         if maxcommitemails is not None:
             try:
@@ -2415,7 +2592,6 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
                                  ['author'])
         self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
 
-        from_addr = self.config.get('from')
         self.from_refchange = config.get('fromRefchange')
         self.forbid_field_values('fromRefchange',
                                  self.from_refchange,
@@ -3390,6 +3566,8 @@ def run_as_post_receive_hook(environment, mailer):
     if changes:
         push = Push(environment, changes)
         push.send_emails(mailer, body_filter=environment.filter_body)
+    if hasattr(mailer, '__del__'):
+        mailer.__del__()
 
 
 def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
@@ -3406,6 +3584,8 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
         ]
     push = Push(environment, changes, force_send)
     push.send_emails(mailer, body_filter=environment.filter_body)
+    if hasattr(mailer, '__del__'):
+        mailer.__del__()
 
 
 def choose_mailer(config, environment):
@@ -3418,6 +3598,7 @@ def choose_mailer(config, environment):
         smtpencryption = config.get('smtpencryption', default='none')
         smtpuser = config.get('smtpuser', default='')
         smtppass = config.get('smtppass', default='')
+        smtpcacerts = config.get('smtpcacerts', default='')
         mailer = SMTPMailer(
             envelopesender=(environment.get_sender() or environment.get_fromaddr()),
             smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
@@ -3425,6 +3606,7 @@ def choose_mailer(config, environment):
             smtpencryption=smtpencryption,
             smtpuser=smtpuser,
             smtppass=smtppass,
+            smtpcacerts=smtpcacerts
             )
     elif mailer == 'sendmail':
         command = config.get('sendmailcommand')
@@ -3691,17 +3873,7 @@ def main(args):
         return
 
     if options.c:
-        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
-        if parameters:
-            parameters += ' '
-        # git expects GIT_CONFIG_PARAMETERS to be of the form
-        #    "'name1=value1' 'name2=value2' 'name3=value3'"
-        # including everything inside the double quotes (but not the double
-        # quotes themselves).  Spacing is critical.  Also, if a value contains
-        # a literal single quote that quote must be represented using the
-        # four character sequence: '\''
-        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c)
-        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+        Config.add_config_parameters(options.c)
 
     config = Config('multimailhook')
 
index 9975df7..1ea113d 100755 (executable)
@@ -55,6 +55,12 @@ import git_multimail
 # git-multimail:
 config = git_multimail.Config('multimailhook')
 
+# Set some Git configuration variables. Equivalent to passing var=val
+# to "git -c var=val" each time git is called, or to adding the
+# configuration in .git/config (must come before instanciating the
+# environment) :
+#git_multimail.Config.add_config_parameters('multimailhook.commitEmailFormat=html')
+#git_multimail.Config.add_config_parameters(('user.name=foo', 'user.email=foo@example.com'))
 
 # Select the type of environment:
 try:
index f4afdc6..86e21de 100644 (file)
@@ -32,6 +32,7 @@ static int send_request(const char *socket, const struct strbuf *out)
                write_or_die(1, in, r);
                got_data = 1;
        }
+       close(fd);
        return got_data;
 }
 
diff --git a/dir.c b/dir.c
index 996653b..656f272 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -64,13 +64,6 @@ int strncmp_icase(const char *a, const char *b, size_t count)
        return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
 }
 
-int fnmatch_icase(const char *pattern, const char *string, int flags)
-{
-       return wildmatch(pattern, string,
-                        flags | (ignore_case ? WM_CASEFOLD : 0),
-                        NULL);
-}
-
 int git_fnmatch(const struct pathspec_item *item,
                const char *pattern, const char *string,
                int prefix)
diff --git a/dir.h b/dir.h
index 301b737..d56d2fb 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -272,7 +272,6 @@ extern int remove_path(const char *path);
 
 extern int strcmp_icase(const char *a, const char *b);
 extern int strncmp_icase(const char *a, const char *b, size_t count);
-extern int fnmatch_icase(const char *pattern, const char *string, int flags);
 
 /*
  * The prefix part of pattern must not contains wildcards.
index 6cc0a77..57acb2f 100644 (file)
@@ -25,11 +25,9 @@ int log_all_ref_updates = -1; /* unspecified */
 int warn_ambiguous_refs = 1;
 int warn_on_object_refname_ambiguity = 1;
 int ref_paranoia = -1;
-int repository_format_version;
 int repository_format_precious_objects;
 const char *git_commit_encoding;
 const char *git_log_output_encoding;
-int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
 const char *apply_default_ignorewhitespace;
 const char *git_attributes_file;
@@ -324,3 +322,24 @@ const char *get_commit_output_encoding(void)
 {
        return git_commit_encoding ? git_commit_encoding : "UTF-8";
 }
+
+static int the_shared_repository = PERM_UMASK;
+static int need_shared_repository_from_config = 1;
+
+void set_shared_repository(int value)
+{
+       the_shared_repository = value;
+       need_shared_repository_from_config = 0;
+}
+
+int get_shared_repository(void)
+{
+       if (need_shared_repository_from_config) {
+               const char *var = "core.sharedrepository";
+               const char *value;
+               if (!git_config_get_value(var, &value))
+                       the_shared_repository = git_config_perm(var, value);
+               need_shared_repository_from_config = 0;
+       }
+       return the_shared_repository;
+}
index f96f6df..b501d5c 100644 (file)
@@ -15,7 +15,6 @@
 #include "version.h"
 #include "prio-queue.h"
 #include "sha1-array.h"
-#include "sigchain.h"
 
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
@@ -674,10 +673,8 @@ static int sideband_demux(int in, int out, void *data)
        int *xd = data;
        int ret;
 
-       sigchain_push(SIGPIPE, SIG_IGN);
        ret = recv_sideband("fetch-pack", xd[0], out);
        close(out);
-       sigchain_pop(SIGPIPE);
        return ret;
 }
 
@@ -701,6 +698,7 @@ static int get_pack(struct fetch_pack_args *args,
                demux.proc = sideband_demux;
                demux.data = xd;
                demux.out = -1;
+               demux.isolate_sigpipe = 1;
                if (start_async(&demux))
                        die("fetch-pack: unable to fork off sideband"
                            " demultiplexer");
index 4743954..1f8b5f3 100644 (file)
@@ -279,9 +279,6 @@ extern char *gitdirname(char *);
 #endif
 #include <openssl/ssl.h>
 #include <openssl/err.h>
-#ifdef NO_HMAC_CTX_CLEANUP
-#define HMAC_CTX_cleanup HMAC_cleanup
-#endif
 #endif
 
 /* On most systems <netdb.h> would have given us this, but
index 2b11b1d..84d6cc0 100755 (executable)
@@ -44,10 +44,10 @@ launch_merge_tool () {
                        "$GIT_DIFF_PATH_TOTAL" "$MERGED"
                if use_ext_cmd
                then
-                       printf "Launch '%s' [Y/n]: " \
+                       printf "Launch '%s' [Y/n]? " \
                                "$GIT_DIFFTOOL_EXTCMD"
                else
-                       printf "Launch '%s' [Y/n]: " "$merge_tool"
+                       printf "Launch '%s' [Y/n]? " "$merge_tool"
                fi
                read ans || return
                if test "$ans" = n
index 54ac8e4..92adcc0 100644 (file)
@@ -100,7 +100,7 @@ check_unchanged () {
                while true
                do
                        echo "$MERGED seems unchanged."
-                       printf "Was the merge successful? [y/n] "
+                       printf "Was the merge successful [y/n]? "
                        read answer || return 1
                        case "$answer" in
                        y*|Y*) return 0 ;;
index f67bab5..bf86270 100755 (executable)
@@ -413,7 +413,7 @@ done
 prompt_after_failed_merge () {
        while true
        do
-               printf "Continue merging other unresolved paths (y/n) ? "
+               printf "Continue merging other unresolved paths [y/n]? "
                read ans || return 1
                case "$ans" in
                [yY]*)
index 825b9f3..e752153 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -1064,8 +1064,15 @@ class GitLFS(LargeFileSystem):
         if pointerProcess.wait():
             os.remove(contentFile)
             die('git-lfs pointer command failed. Did you install the extension?')
-        pointerContents = [i+'\n' for i in pointerFile.split('\n')[2:][:-1]]
-        oid = pointerContents[1].split(' ')[1].split(':')[1][:-1]
+
+        # Git LFS removed the preamble in the output of the 'pointer' command
+        # starting from version 1.2.0. Check for the preamble here to support
+        # earlier versions.
+        # c.f. https://github.com/github/git-lfs/commit/da2935d9a739592bc775c98d8ef4df9c72ea3b43
+        if pointerFile.startswith('Git LFS pointer for'):
+            pointerFile = re.sub(r'Git LFS pointer for.*\n\n', '', pointerFile)
+
+        oid = re.search(r'^oid \w+:(\w+)', pointerFile, re.MULTILINE).group(1)
         localLargeFile = os.path.join(
             os.getcwd(),
             '.git', 'lfs', 'objects', oid[:2], oid[2:4],
@@ -1073,7 +1080,7 @@ class GitLFS(LargeFileSystem):
         )
         # LFS Spec states that pointer files should not have the executable bit set.
         gitMode = '100644'
-        return (gitMode, pointerContents, localLargeFile)
+        return (gitMode, pointerFile, localLargeFile)
 
     def pushFile(self, localLargeFile):
         uploadProcess = subprocess.Popen(
index c45b22a..6958785 100755 (executable)
 use 5.008;
 use strict;
 use warnings;
+use POSIX qw/strftime/;
 use Term::ReadLine;
 use Getopt::Long;
 use Text::ParseWords;
-use Data::Dumper;
 use Term::ANSIColor;
 use File::Temp qw/ tempdir tempfile /;
 use File::Spec::Functions qw(catfile);
@@ -827,9 +827,10 @@ if (defined $sender) {
 # But it's a no-op to run sanitize_address on an already sanitized address.
 $sender = sanitize_address($sender);
 
+my $to_whom = "To whom should the emails be sent (if anyone)?";
 my $prompting = 0;
 if (!@initial_to && !defined $to_cmd) {
-       my $to = ask("Who should the emails be sent to (if any)? ",
+       my $to = ask("$to_whom ",
                     default => "",
                     valid_re => qr/\@.*\./, confirm_only => 1);
        push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
@@ -924,7 +925,7 @@ sub validate_address {
                        cleanup_compose_files();
                        exit(0);
                }
-               $address = ask("Who should the email be sent to (if any)? ",
+               $address = ask("$to_whom ",
                        default => "",
                        valid_re => qr/\@.*\./, confirm_only => 1);
        }
@@ -949,7 +950,7 @@ my ($message_id_stamp, $message_id_serial);
 sub make_message_id {
        my $uniq;
        if (!defined $message_id_stamp) {
-               $message_id_stamp = sprintf("%s-%s", time, $$);
+               $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time));
                $message_id_serial = 0;
        }
        $message_id_serial++;
@@ -964,7 +965,7 @@ sub make_message_id {
                require Sys::Hostname;
                $du_part = 'user@' . Sys::Hostname::hostname();
        }
-       my $message_id_template = "<%s-git-send-email-%s>";
+       my $message_id_template = "<%s-%s>";
        $message_id = sprintf($message_id_template, $uniq, $du_part);
        #print "new message id = $message_id\n"; # Was useful for debugging
 }
index 43c68de..753a90d 100755 (executable)
@@ -413,8 +413,8 @@ cmd_foreach()
                die_if_unmatched "$mode"
                if test -e "$sm_path"/.git
                then
-                       displaypath=$(relative_path "$sm_path")
-                       say "$(eval_gettext "Entering '\$prefix\$displaypath'")"
+                       displaypath=$(relative_path "$prefix$sm_path")
+                       say "$(eval_gettext "Entering '\$displaypath'")"
                        name=$(git submodule--helper name "$sm_path")
                        (
                                prefix="$prefix$sm_path/"
@@ -434,7 +434,7 @@ cmd_foreach()
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")"
+                       die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")"
                fi
        done
 }
@@ -473,7 +473,7 @@ cmd_init()
                die_if_unmatched "$mode"
                name=$(git submodule--helper name "$sm_path") || exit
 
-               displaypath=$(relative_path "$sm_path")
+               displaypath=$(relative_path "$prefix$sm_path")
 
                # Copy url setting when it is not set yet
                if test -z "$(git config "submodule.$name.url")"
@@ -802,8 +802,8 @@ Maybe you want to use 'update --init'?")"
                                ;;
                        !*)
                                command="${update_module#!}"
-                               die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$prefix\$sm_path'")"
-                               say_msg="$(eval_gettext "Submodule path '\$prefix\$sm_path': '\$command \$sha1'")"
+                               die_msg="$(eval_gettext "Execution of '\$command \$sha1' failed in submodule path '\$displaypath'")"
+                               say_msg="$(eval_gettext "Submodule path '\$displaypath': '\$command \$sha1'")"
                                must_die_on_failure=yes
                                ;;
                        *)
@@ -1159,6 +1159,7 @@ cmd_status()
                        (
                                prefix="$displaypath/"
                                clear_local_git_env
+                               wt_prefix=
                                cd "$sm_path" &&
                                eval cmd_status
                        ) ||
index 05d7910..2fddf75 100755 (executable)
@@ -3935,6 +3935,9 @@ sub run_highlighter {
 
        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");
diff --git a/http.c b/http.c
index 69da445..4304b80 100644 (file)
--- a/http.c
+++ b/http.c
@@ -605,7 +605,10 @@ static CURL *get_curl_handle(void)
        if (curl_http_proxy) {
                curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
 #if LIBCURL_VERSION_NUM >= 0x071800
-               if (starts_with(curl_http_proxy, "socks5"))
+               if (starts_with(curl_http_proxy, "socks5h"))
+                       curl_easy_setopt(result,
+                               CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
+               else if (starts_with(curl_http_proxy, "socks5"))
                        curl_easy_setopt(result,
                                CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
                else if (starts_with(curl_http_proxy, "socks4a"))
diff --git a/ident.c b/ident.c
index 6e12582..4fd82d1 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -351,15 +351,17 @@ const char *fmt_ident(const char *name, const char *email,
        if (want_name) {
                int using_default = 0;
                if (!name) {
+                       if (strict && ident_use_config_only
+                           && !(ident_config_given & IDENT_NAME_GIVEN)) {
+                               fputs(env_hint, stderr);
+                               die("no name was given and auto-detection is disabled");
+                       }
                        name = ident_default_name();
                        using_default = 1;
                        if (strict && default_name_is_bogus) {
                                fputs(env_hint, stderr);
                                die("unable to auto-detect name (got '%s')", name);
                        }
-                       if (strict && ident_use_config_only
-                           && !(ident_config_given & IDENT_NAME_GIVEN))
-                               die("user.useConfigOnly set but no name given");
                }
                if (!*name) {
                        struct passwd *pw;
@@ -374,14 +376,16 @@ const char *fmt_ident(const char *name, const char *email,
        }
 
        if (!email) {
+               if (strict && ident_use_config_only
+                   && !(ident_config_given & IDENT_MAIL_GIVEN)) {
+                       fputs(env_hint, stderr);
+                       die("no email was given and auto-detection is disabled");
+               }
                email = ident_default_email();
                if (strict && default_email_is_bogus) {
                        fputs(env_hint, stderr);
                        die("unable to auto-detect email address (got '%s')", email);
                }
-               if (strict && ident_use_config_only
-                   && !(ident_config_given & IDENT_MAIL_GIVEN))
-                       die("user.useConfigOnly set but no mail given");
        }
 
        strbuf_reset(&ident);
index 2c52027..938c691 100644 (file)
@@ -287,17 +287,20 @@ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int ve
        SSL_library_init();
        SSL_load_error_strings();
 
-       if (use_tls_only)
-               meth = TLSv1_method();
-       else
-               meth = SSLv23_method();
-
+       meth = SSLv23_method();
        if (!meth) {
                ssl_socket_perror("SSLv23_method");
                return -1;
        }
 
        ctx = SSL_CTX_new(meth);
+       if (!ctx) {
+               ssl_socket_perror("SSL_CTX_new");
+               return -1;
+       }
+
+       if (use_tls_only)
+               SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
 
        if (verify)
                SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
@@ -862,7 +865,6 @@ static char hexchar(unsigned int b)
 static char *cram(const char *challenge_64, const char *user, const char *pass)
 {
        int i, resp_len, encoded_len, decoded_len;
-       HMAC_CTX hmac;
        unsigned char hash[16];
        char hex[33];
        char *response, *response_64, *challenge;
@@ -877,10 +879,8 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
                                      (unsigned char *)challenge_64, encoded_len);
        if (decoded_len < 0)
                die("invalid challenge %s", challenge_64);
-       HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
-       HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
-       HMAC_Final(&hmac, hash, NULL);
-       HMAC_CTX_cleanup(&hmac);
+       if (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))
+               die("HMAC error");
 
        hex[32] = 0;
        for (i = 0; i < 16; i++) {
@@ -890,7 +890,7 @@ static char *cram(const char *challenge_64, const char *user, const char *pass)
 
        /* response: "<user> <digest in hex>" */
        response = xstrfmt("%s %s", user, hex);
-       resp_len = strlen(response) + 1;
+       resp_len = strlen(response);
 
        response_64 = xmallocz(ENCODED_SIZE(resp_len));
        encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
@@ -1095,11 +1095,6 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, char *f
                                srvc->pass = xstrdup(cred.password);
                }
 
-               if (CAP(NOLOGIN)) {
-                       fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
-                       goto bail;
-               }
-
                if (srvc->auth_method) {
                        struct imap_cmd_cb cb;
 
@@ -1123,6 +1118,11 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, char *f
                                goto bail;
                        }
                } else {
+                       if (CAP(NOLOGIN)) {
+                               fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n",
+                                       srvc->user, srvc->host);
+                               goto bail;
+                       }
                        if (!imap->buf.sock.ssl)
                                imap_warn("*** IMAP Warning *** Password is being "
                                          "sent in the clear\n");
diff --git a/path.c b/path.c
index 969b494..bbaea5a 100644 (file)
--- a/path.c
+++ b/path.c
@@ -702,17 +702,17 @@ static int calc_shared_perm(int mode)
 {
        int tweak;
 
-       if (shared_repository < 0)
-               tweak = -shared_repository;
+       if (get_shared_repository() < 0)
+               tweak = -get_shared_repository();
        else
-               tweak = shared_repository;
+               tweak = get_shared_repository();
 
        if (!(mode & S_IWUSR))
                tweak &= ~0222;
        if (mode & S_IXUSR)
                /* Copy read bits to execute bits */
                tweak |= (tweak & 0444) >> 2;
-       if (shared_repository < 0)
+       if (get_shared_repository() < 0)
                mode = (mode & ~0777) | tweak;
        else
                mode |= tweak;
@@ -725,7 +725,7 @@ int adjust_shared_perm(const char *path)
 {
        int old_mode, new_mode;
 
-       if (!shared_repository)
+       if (!get_shared_repository())
                return 0;
        if (get_st_mode_bits(path, &old_mode) < 0)
                return -1;
diff --git a/refs.h b/refs.h
index 2f3decb..9230d47 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -306,6 +306,15 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
 
 extern int create_symref(const char *refname, const char *target, const char *logmsg);
 
+/*
+ * Update HEAD of the specified gitdir.
+ * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but
+ * this can update the main working tree's HEAD regardless of where
+ * $GIT_DIR points to.
+ * Return 0 if successful, non-zero otherwise.
+ * */
+extern int set_worktree_head_symref(const char *gitdir, const char *target);
+
 enum action_on_err {
        UPDATE_REFS_MSG_ON_ERR,
        UPDATE_REFS_DIE_ON_ERR,
index 81f68f8..ea78ce9 100644 (file)
@@ -2894,6 +2894,42 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
        return ret;
 }
 
+int set_worktree_head_symref(const char *gitdir, const char *target)
+{
+       static struct lock_file head_lock;
+       struct ref_lock *lock;
+       struct strbuf head_path = STRBUF_INIT;
+       const char *head_rel;
+       int ret;
+
+       strbuf_addf(&head_path, "%s/HEAD", absolute_path(gitdir));
+       if (hold_lock_file_for_update(&head_lock, head_path.buf,
+                                     LOCK_NO_DEREF) < 0) {
+               struct strbuf err = STRBUF_INIT;
+               unable_to_lock_message(head_path.buf, errno, &err);
+               error("%s", err.buf);
+               strbuf_release(&err);
+               strbuf_release(&head_path);
+               return -1;
+       }
+
+       /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for
+          linked trees */
+       head_rel = remove_leading_path(head_path.buf,
+                                      absolute_path(get_git_common_dir()));
+       /* to make use of create_symref_locked(), initialize ref_lock */
+       lock = xcalloc(1, sizeof(struct ref_lock));
+       lock->lk = &head_lock;
+       lock->ref_name = xstrdup(head_rel);
+       lock->orig_ref_name = xstrdup(head_rel);
+
+       ret = create_symref_locked(lock, head_rel, target, NULL);
+
+       unlock_ref(lock); /* will free lock */
+       strbuf_release(&head_path);
+       return ret;
+}
+
 int reflog_exists(const char *refname)
 {
        struct stat st;
index 28fd676..ddc4f8f 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1660,7 +1660,7 @@ int branch_merge_matches(struct branch *branch,
        return refname_match(branch->merge[i]->src, refname);
 }
 
-__attribute((format (printf,2,3)))
+__attribute__((format (printf,2,3)))
 static const char *error_buf(struct strbuf *err, const char *fmt, ...)
 {
        if (err) {
index c726010..2d66280 100644 (file)
@@ -590,6 +590,16 @@ static void *run_thread(void *data)
        struct async *async = data;
        intptr_t ret;
 
+       if (async->isolate_sigpipe) {
+               sigset_t mask;
+               sigemptyset(&mask);
+               sigaddset(&mask, SIGPIPE);
+               if (pthread_sigmask(SIG_BLOCK, &mask, NULL) < 0) {
+                       ret = error("unable to block SIGPIPE in async thread");
+                       return (void *)ret;
+               }
+       }
+
        pthread_setspecific(async_key, async);
        ret = async->proc(async->proc_in, async->proc_out, async->data);
        return (void *)ret;
index 3d1e59e..49ba764 100644 (file)
@@ -116,6 +116,7 @@ struct async {
        int proc_in;
        int proc_out;
 #endif
+       int isolate_sigpipe;
 };
 
 int start_async(struct async *async);
index 047bd18..37ee04e 100644 (file)
@@ -518,6 +518,7 @@ int send_pack(struct send_pack_args *args,
                demux.proc = sideband_demux;
                demux.data = fd;
                demux.out = -1;
+               demux.isolate_sigpipe = 1;
                if (start_async(&demux))
                        die("send-pack: unable to fork off sideband demultiplexer");
                in = demux.out;
@@ -531,8 +532,10 @@ int send_pack(struct send_pack_args *args,
                                close(out);
                        if (git_connection_is_socket(conn))
                                shutdown(fd[0], SHUT_WR);
-                       if (use_sideband)
+                       if (use_sideband) {
+                               close(demux.out);
                                finish_async(&demux);
+                       }
                        fd[1] = -1;
                        return -1;
                }
@@ -551,11 +554,11 @@ int send_pack(struct send_pack_args *args,
                packet_flush(out);
 
        if (use_sideband && cmds_sent) {
+               close(demux.out);
                if (finish_async(&demux)) {
                        error("error in sideband demultiplexer");
                        ret = -1;
                }
-               close(demux.out);
        }
 
        if (ret < 0)
diff --git a/setup.c b/setup.c
index 3439ec6..c86bf5c 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -5,7 +5,6 @@
 static int inside_git_dir = -1;
 static int inside_work_tree = -1;
 static int work_tree_config_is_bogus;
-static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;
 
 static struct startup_info the_startup_info;
 struct startup_info *startup_info = &the_startup_info;
@@ -103,7 +102,7 @@ char *prefix_path_gently(const char *prefix, int len,
                        return NULL;
                }
        } else {
-               sanitized = xstrfmt("%.*s%s", len, prefix, path);
+               sanitized = xstrfmt("%.*s%s", len, len ? prefix : "", path);
                if (remaining_prefix)
                        *remaining_prefix = len;
                if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
@@ -373,14 +372,13 @@ void setup_work_tree(void)
        initialized = 1;
 }
 
-static int check_repo_format(const char *var, const char *value, void *cb)
+static int check_repo_format(const char *var, const char *value, void *vdata)
 {
+       struct repository_format *data = vdata;
        const char *ext;
 
        if (strcmp(var, "core.repositoryformatversion") == 0)
-               repository_format_version = git_config_int(var, value);
-       else if (strcmp(var, "core.sharedrepository") == 0)
-               shared_repository = git_config_perm(var, value);
+               data->version = git_config_int(var, value);
        else if (skip_prefix(var, "extensions.", &ext)) {
                /*
                 * record any known extensions here; otherwise,
@@ -390,9 +388,15 @@ static int check_repo_format(const char *var, const char *value, void *cb)
                if (!strcmp(ext, "noop"))
                        ;
                else if (!strcmp(ext, "preciousobjects"))
-                       repository_format_precious_objects = git_config_bool(var, value);
+                       data->precious_objects = git_config_bool(var, value);
                else
-                       string_list_append(&unknown_extensions, ext);
+                       string_list_append(&data->unknown_extensions, ext);
+       } else if (strcmp(var, "core.bare") == 0) {
+               data->is_bare = git_config_bool(var, value);
+       } else if (strcmp(var, "core.worktree") == 0) {
+               if (!value)
+                       return config_error_nonbool(var);
+               data->work_tree = xstrdup(value);
        }
        return 0;
 }
@@ -400,56 +404,84 @@ static int check_repo_format(const char *var, const char *value, void *cb)
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
        struct strbuf sb = STRBUF_INIT;
-       const char *repo_config;
-       config_fn_t fn;
-       int ret = 0;
-
-       string_list_clear(&unknown_extensions, 0);
+       struct strbuf err = STRBUF_INIT;
+       struct repository_format candidate;
+       int has_common;
 
-       if (get_common_dir(&sb, gitdir))
-               fn = check_repo_format;
-       else
-               fn = check_repository_format_version;
+       has_common = get_common_dir(&sb, gitdir);
        strbuf_addstr(&sb, "/config");
-       repo_config = sb.buf;
+       read_repository_format(&candidate, sb.buf);
+       strbuf_release(&sb);
 
        /*
-        * git_config() can't be used here because it calls git_pathdup()
-        * to get $GIT_CONFIG/config. That call will make setup_git_env()
-        * set git_dir to ".git".
-        *
-        * We are in gitdir setup, no git dir has been found useable yet.
-        * Use a gentler version of git_config() to check if this repo
-        * is a good one.
+        * For historical use of check_repository_format() in git-init,
+        * we treat a missing config as a silent "ok", even when nongit_ok
+        * is unset.
         */
-       git_config_early(fn, NULL, repo_config);
-       if (GIT_REPO_VERSION_READ < repository_format_version) {
-               if (!nongit_ok)
-                       die ("Expected git repo version <= %d, found %d",
-                            GIT_REPO_VERSION_READ, repository_format_version);
-               warning("Expected git repo version <= %d, found %d",
-                       GIT_REPO_VERSION_READ, repository_format_version);
-               warning("Please upgrade Git");
-               *nongit_ok = -1;
-               ret = -1;
+       if (candidate.version < 0)
+               return 0;
+
+       if (verify_repository_format(&candidate, &err) < 0) {
+               if (nongit_ok) {
+                       warning("%s", err.buf);
+                       strbuf_release(&err);
+                       *nongit_ok = -1;
+                       return -1;
+               }
+               die("%s", err.buf);
        }
 
-       if (repository_format_version >= 1 && unknown_extensions.nr) {
+       repository_format_precious_objects = candidate.precious_objects;
+       string_list_clear(&candidate.unknown_extensions, 0);
+       if (!has_common) {
+               if (candidate.is_bare != -1) {
+                       is_bare_repository_cfg = candidate.is_bare;
+                       if (is_bare_repository_cfg == 1)
+                               inside_work_tree = -1;
+               }
+               if (candidate.work_tree) {
+                       free(git_work_tree_cfg);
+                       git_work_tree_cfg = candidate.work_tree;
+                       inside_work_tree = -1;
+               }
+       } else {
+               free(candidate.work_tree);
+       }
+
+       return 0;
+}
+
+int read_repository_format(struct repository_format *format, const char *path)
+{
+       memset(format, 0, sizeof(*format));
+       format->version = -1;
+       format->is_bare = -1;
+       string_list_init(&format->unknown_extensions, 1);
+       git_config_from_file(check_repo_format, path, format);
+       return format->version;
+}
+
+int verify_repository_format(const struct repository_format *format,
+                            struct strbuf *err)
+{
+       if (GIT_REPO_VERSION_READ < format->version) {
+               strbuf_addf(err, _("Expected git repo version <= %d, found %d"),
+                           GIT_REPO_VERSION_READ, format->version);
+               return -1;
+       }
+
+       if (format->version >= 1 && format->unknown_extensions.nr) {
                int i;
 
-               if (!nongit_ok)
-                       die("unknown repository extension: %s",
-                           unknown_extensions.items[0].string);
+               strbuf_addstr(err, _("unknown repository extensions found:"));
 
-               for (i = 0; i < unknown_extensions.nr; i++)
-                       warning("unknown repository extension: %s",
-                               unknown_extensions.items[i].string);
-               *nongit_ok = -1;
-               ret = -1;
+               for (i = 0; i < format->unknown_extensions.nr; i++)
+                       strbuf_addf(err, "\n\t%s",
+                                   format->unknown_extensions.items[i].string);
+               return -1;
        }
 
-       strbuf_release(&sb);
-       return ret;
+       return 0;
 }
 
 /*
@@ -965,30 +997,10 @@ int git_config_perm(const char *var, const char *value)
        return -(i & 0666);
 }
 
-int check_repository_format_version(const char *var, const char *value, void *cb)
-{
-       int ret = check_repo_format(var, value, cb);
-       if (ret)
-               return ret;
-       if (strcmp(var, "core.bare") == 0) {
-               is_bare_repository_cfg = git_config_bool(var, value);
-               if (is_bare_repository_cfg == 1)
-                       inside_work_tree = -1;
-       } else if (strcmp(var, "core.worktree") == 0) {
-               if (!value)
-                       return config_error_nonbool(var);
-               free(git_work_tree_cfg);
-               git_work_tree_cfg = xstrdup(value);
-               inside_work_tree = -1;
-       }
-       return 0;
-}
-
-int check_repository_format(void)
+void check_repository_format(void)
 {
        check_repository_format_gently(get_git_dir(), NULL);
        startup_info->have_repository = 1;
-       return 0;
 }
 
 /*
index 2a32a3f..62d2084 100644 (file)
@@ -231,12 +231,12 @@ void string_list_sort(struct string_list *list)
 struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
                                                     const char *string)
 {
-       int i;
+       struct string_list_item *item;
        compare_strings_fn cmp = list->cmp ? list->cmp : strcmp;
 
-       for (i = 0; i < list->nr; i++)
-               if (!cmp(string, list->items[i].string))
-                       return list->items + i;
+       for_each_string_list_item(item, list)
+               if (!cmp(string, item->string))
+                       return item;
        return NULL;
 }
 
index 8476e0f..7f67ec0 100644 (file)
@@ -30,7 +30,7 @@ enum lookup_type {
        lookup_path
 };
 
-static struct submodule_cache cache;
+static struct submodule_cache the_submodule_cache;
 static int is_cache_init;
 
 static int config_path_cmp(const struct submodule_entry *a,
@@ -457,14 +457,14 @@ static void ensure_cache_init(void)
        if (is_cache_init)
                return;
 
-       cache_init(&cache);
+       cache_init(&the_submodule_cache);
        is_cache_init = 1;
 }
 
 int parse_submodule_config_option(const char *var, const char *value)
 {
        struct parse_config_parameter parameter;
-       parameter.cache = &cache;
+       parameter.cache = &the_submodule_cache;
        parameter.commit_sha1 = NULL;
        parameter.gitmodules_sha1 = null_sha1;
        parameter.overwrite = 1;
@@ -477,18 +477,18 @@ const struct submodule *submodule_from_name(const unsigned char *commit_sha1,
                const char *name)
 {
        ensure_cache_init();
-       return config_from_name(&cache, commit_sha1, name);
+       return config_from_name(&the_submodule_cache, commit_sha1, name);
 }
 
 const struct submodule *submodule_from_path(const unsigned char *commit_sha1,
                const char *path)
 {
        ensure_cache_init();
-       return config_from_path(&cache, commit_sha1, path);
+       return config_from_path(&the_submodule_cache, commit_sha1, path);
 }
 
 void submodule_free(void)
 {
-       cache_free(&cache);
+       cache_free(&the_submodule_cache);
        is_cache_init = 0;
 }
index f9ae1d7..012d40e 100644 (file)
@@ -50,7 +50,7 @@ native_path() {
 # 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
@@ -198,9 +198,10 @@ marshal_dump() {
        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"
 }
index 8e22b03..df3183e 100755 (executable)
@@ -141,13 +141,13 @@ test_expect_success 'GIT_PREFIX for !alias' '
 test_expect_success 'GIT_PREFIX for built-ins' '
        # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
        # receives the GIT_PREFIX variable.
-       printf "dir/" >expect &&
-       printf "#!/bin/sh\n" >diff &&
-       printf "printf \"\$GIT_PREFIX\"" >>diff &&
-       chmod +x diff &&
+       echo "dir/" >expect &&
+       write_script diff <<-\EOF &&
+       printf "%s\n" "$GIT_PREFIX"
+       EOF
        (
                cd dir &&
-               printf "change" >two &&
+               echo "change" >two &&
                GIT_EXTERNAL_DIFF=./diff git diff >../actual
                git checkout -- two
        ) &&
index a897248..f3e3b6c 100755 (executable)
@@ -126,7 +126,28 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out'
 test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
        git checkout -b baz &&
        git branch bam &&
-       git branch -M baz bam
+       git branch -M baz bam &&
+       test $(git rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '
+       git checkout master &&
+       git worktree add -b baz bazdir &&
+       git worktree add -f bazdir2 baz &&
+       git branch -M baz bam &&
+       test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam &&
+       test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
+       git checkout -b baz &&
+       git worktree add -f bazdir3 baz &&
+       (
+               cd bazdir3 &&
+               git branch -M baz bam &&
+               test $(git rev-parse --abbrev-ref HEAD) = bam
+       ) &&
+       test $(git rev-parse --abbrev-ref HEAD) = bam
 '
 
 test_expect_success 'git branch -M master should work when master is checked out' '
@@ -403,6 +424,12 @@ test_expect_success 'test deleting branch without config' '
        test_i18ncmp expect actual
 '
 
+test_expect_success 'deleting currently checked out branch fails' '
+       git worktree add -b my7 my7 &&
+       test_must_fail git -C my7 branch -d my7 &&
+       test_must_fail git branch -d my7
+'
+
 test_expect_success 'test --track without .fetch entries' '
        git branch --track my8 &&
        test "$(git config branch.my8.remote)" &&
index 4261403..c6a3ccb 100755 (executable)
@@ -184,4 +184,16 @@ test_expect_success 'ambiguous branch/tag not marked' '
        test_cmp expect actual
 '
 
+test_expect_success 'local-branch symrefs shortened properly' '
+       git symbolic-ref refs/heads/ref-to-branch refs/heads/branch-one &&
+       git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one &&
+       cat >expect <<-\EOF &&
+         ref-to-branch -> branch-one
+         ref-to-remote -> refs/remotes/origin/branch-one
+       EOF
+       git branch >actual.raw &&
+       grep ref-to <actual.raw >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 544f9ad..d6d65a3 100755 (executable)
@@ -555,10 +555,9 @@ test_expect_success 'rebase a detached HEAD' '
 test_expect_success 'rebase a commit violating pre-commit' '
 
        mkdir -p .git/hooks &&
-       PRE_COMMIT=.git/hooks/pre-commit &&
-       echo "#!/bin/sh" > $PRE_COMMIT &&
-       echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT &&
-       chmod a+x $PRE_COMMIT &&
+       write_script .git/hooks/pre-commit <<-\EOF &&
+       test -z "$(git diff --cached --check)"
+       EOF
        echo "monde! " >> file1 &&
        test_tick &&
        test_must_fail git commit -m doesnt-verify file1 &&
index a3e12d2..44f3d5f 100755 (executable)
@@ -100,11 +100,8 @@ test_expect_success 'push with receive.fsckobjects' '
                git config receive.fsckobjects true &&
                git config transfer.fsckobjects false
        ) &&
-       test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act &&
-       {
-               test_cmp exp act ||
-               ! test -s act
-       }
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
 test_expect_success 'push with transfer.fsckobjects' '
@@ -114,7 +111,8 @@ test_expect_success 'push with transfer.fsckobjects' '
                cd dst &&
                git config transfer.fsckobjects true
        ) &&
-       test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act
+       test_must_fail git push --porcelain dst master:refs/heads/test >act &&
+       test_cmp exp act
 '
 
 cat >bogus-commit <<\EOF
index 38321d1..454d896 100755 (executable)
@@ -682,6 +682,7 @@ test_expect_success 'fetching with auto-gc does not lock up' '
        (
                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
        )
index d75ef0e..51c9669 100755 (executable)
@@ -12,10 +12,8 @@ test_expect_success 'setup remote repo' '
        )
 '
 
-cat >proxy <<'EOF'
-#!/bin/sh
-echo >&2 "proxying for $*"
-cmd=$("$PERL_PATH" -e '
+test_expect_success 'setup proxy script' '
+       write_script proxy-get-cmd "$PERL_PATH" <<-\EOF &&
        read(STDIN, $buf, 4);
        my $n = hex($buf) - 4;
        read(STDIN, $buf, $n);
@@ -23,11 +21,16 @@ cmd=$("$PERL_PATH" -e '
        # drop absolute-path on repo name
        $cmd =~ s{ /}{ };
        print $cmd;
-')
-echo >&2 "Running '$cmd'"
-exec $cmd
-EOF
-chmod +x proxy
+       EOF
+
+       write_script proxy <<-\EOF
+       echo >&2 "proxying for $*"
+       cmd=$(./proxy-get-cmd)
+       echo >&2 "Running $cmd"
+       exec $cmd
+       EOF
+'
+
 test_expect_success 'setup local repo' '
        git remote add fake git://example.com/remote &&
        git config core.gitproxy ./proxy
index 4008fae..4a2570e 100755 (executable)
@@ -292,6 +292,9 @@ test_expect_success 'setup submodule' '
        echo content >file &&
        git add file &&
        git commit -m "added sub and file" &&
+       mkdir -p deep/directory/hierachy &&
+       git submodule add ./. deep/directory/hierachy/sub &&
+       git commit -m "added another submodule" &&
        git branch submodule
 '
 
@@ -475,4 +478,17 @@ test_expect_success 'mv -k does not accidentally destroy submodules' '
        git checkout .
 '
 
+test_expect_success 'moving a submodule in nested directories' '
+       (
+               cd deep &&
+               git mv directory ../ &&
+               # git status would fail if the update of linking git dir to
+               # work dir of the submodule failed.
+               git status &&
+               git config -f ../.gitmodules submodule.deep/directory/hierachy/sub.path >../actual &&
+               echo "directory/hierachy/sub" >../expect
+       ) &&
+       test_cmp actual expect
+'
+
 test_done
index 86ceb38..b89fd2a 100755 (executable)
@@ -495,7 +495,7 @@ test_expect_success 'should not clean submodules' '
        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*" &&
index e1abd19..a41be31 100755 (executable)
@@ -818,6 +818,47 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano
        )
 '
 
+test_expect_success 'recursive relative submodules stay relative' '
+       test_when_finished "rm -rf super clone2 subsub sub3" &&
+       mkdir subsub &&
+       (
+               cd subsub &&
+               git init &&
+               >t &&
+               git add t &&
+               git commit -m "initial commit"
+       ) &&
+       mkdir sub3 &&
+       (
+               cd sub3 &&
+               git init &&
+               >t &&
+               git add t &&
+               git commit -m "initial commit" &&
+               git submodule add ../subsub dirdir/subsub &&
+               git commit -m "add submodule subsub"
+       ) &&
+       mkdir super &&
+       (
+               cd super &&
+               git init &&
+               >t &&
+               git add t &&
+               git commit -m "initial commit" &&
+               git submodule add ../sub3 &&
+               git commit -m "add submodule sub"
+       ) &&
+       git clone super clone2 &&
+       (
+               cd clone2 &&
+               git submodule update --init --recursive &&
+               echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect &&
+               echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect
+       ) &&
+       test_cmp clone2/sub3/.git_expect clone2/sub3/.git &&
+       test_cmp clone2/sub3/dirdir/subsub/.git_expect clone2/sub3/dirdir/subsub/.git
+'
+
 test_expect_success 'submodule add with an existing name fails unless forced' '
        (
                cd addtest2 &&
index 68ea31d..e5af4b4 100755 (executable)
@@ -63,6 +63,10 @@ test_expect_success 'setup a submodule tree' '
         git submodule add ../none none &&
         test_tick &&
         git commit -m "none"
+       ) &&
+       git clone . recursivesuper &&
+       ( cd recursivesuper
+        git submodule add ../super super
        )
 '
 
@@ -95,6 +99,35 @@ test_expect_success 'submodule update from subdirectory' '
        )
 '
 
+supersha1=$(git -C super rev-parse HEAD)
+mergingsha1=$(git -C super/merging rev-parse HEAD)
+nonesha1=$(git -C super/none rev-parse HEAD)
+rebasingsha1=$(git -C super/rebasing rev-parse HEAD)
+submodulesha1=$(git -C super/submodule rev-parse HEAD)
+pwd=$(pwd)
+
+cat <<EOF >expect
+Submodule path '../super': checked out '$supersha1'
+Submodule 'merging' ($pwd/merging) registered for path '../super/merging'
+Submodule 'none' ($pwd/none) registered for path '../super/none'
+Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing'
+Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule'
+Submodule path '../super/merging': checked out '$mergingsha1'
+Submodule path '../super/none': checked out '$nonesha1'
+Submodule path '../super/rebasing': checked out '$rebasingsha1'
+Submodule path '../super/submodule': checked out '$submodulesha1'
+EOF
+
+test_expect_success 'submodule update --init --recursive from subdirectory' '
+       git -C recursivesuper/super reset --hard HEAD^ &&
+       (cd recursivesuper &&
+        mkdir tmp &&
+        cd tmp &&
+        git submodule update --init --recursive ../super >../../actual
+       ) &&
+       test_cmp expect actual
+'
+
 apos="'";
 test_expect_success 'submodule update does not fetch already present commits' '
        (cd submodule &&
@@ -311,16 +344,59 @@ test_expect_success 'submodule update - command in .git/config' '
        )
 '
 
+cat << EOF >expect
+Execution of 'false $submodulesha1' failed in submodule path 'submodule'
+EOF
+
 test_expect_success 'submodule update - command in .git/config catches failure' '
        (cd super &&
         git config submodule.submodule.update "!false"
        ) &&
        (cd super/submodule &&
-         git reset --hard HEAD^
+         git reset --hard $submodulesha1^
        ) &&
        (cd super &&
-        test_must_fail git submodule update submodule
-       )
+        test_must_fail git submodule update submodule 2>../actual
+       ) &&
+       test_cmp actual expect
+'
+
+cat << EOF >expect
+Execution of 'false $submodulesha1' failed in submodule path '../submodule'
+EOF
+
+test_expect_success 'submodule update - command in .git/config catches failure -- subdirectory' '
+       (cd super &&
+        git config submodule.submodule.update "!false"
+       ) &&
+       (cd super/submodule &&
+         git reset --hard $submodulesha1^
+       ) &&
+       (cd super &&
+        mkdir tmp && cd tmp &&
+        test_must_fail git submodule update ../submodule 2>../../actual
+       ) &&
+       test_cmp actual expect
+'
+
+cat << EOF >expect
+Execution of 'false $submodulesha1' failed in submodule path '../super/submodule'
+Failed to recurse into submodule path '../super'
+EOF
+
+test_expect_success 'recursive submodule update - command in .git/config catches failure -- subdirectory' '
+       (cd recursivesuper &&
+        git submodule update --remote super &&
+        git add super &&
+        git commit -m "update to latest to have more than one commit in submodules"
+       ) &&
+       git -C recursivesuper/super config submodule.submodule.update "!false" &&
+       git -C recursivesuper/super/submodule reset --hard $submodulesha1^ &&
+       (cd recursivesuper &&
+        mkdir -p tmp && cd tmp &&
+        test_must_fail git submodule update --recursive ../super 2>../../actual
+       ) &&
+       test_cmp actual expect
 '
 
 test_expect_success 'submodule init does not copy command into .git/config' '
index 7ca10b8..6ba5daf 100755 (executable)
@@ -178,6 +178,26 @@ test_expect_success 'test messages from "foreach --recursive"' '
 '
 
 cat > expect <<EOF
+Entering '../nested1'
+Entering '../nested1/nested2'
+Entering '../nested1/nested2/nested3'
+Entering '../nested1/nested2/nested3/submodule'
+Entering '../sub1'
+Entering '../sub2'
+Entering '../sub3'
+EOF
+
+test_expect_success 'test messages from "foreach --recursive" from subdirectory' '
+       (
+               cd clone2 &&
+               mkdir untracked &&
+               cd untracked &&
+               git submodule foreach --recursive >../../actual
+       ) &&
+       test_i18ncmp expect actual
+'
+
+cat > expect <<EOF
 nested1-nested1
 nested2-nested2
 nested3-nested3
@@ -242,8 +262,12 @@ test_expect_success 'test "status --recursive"' '
        test_cmp expect actual
 '
 
-sed -e "/nested2 /s/.*/+$nested2sha1 nested1\/nested2 (file2~1)/;/sub[1-3]/d" < expect > expect2
-mv -f expect2 expect
+cat > expect <<EOF
+ $nested1sha1 nested1 (heads/master)
++$nested2sha1 nested1/nested2 (file2~1)
+ $nested3sha1 nested1/nested2/nested3 (heads/master)
+ $submodulesha1 nested1/nested2/nested3/submodule (heads/master)
+EOF
 
 test_expect_success 'ensure "status --cached --recursive" preserves the --cached flag' '
        (
@@ -257,6 +281,27 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached
        test_cmp expect actual
 '
 
+nested2sha1=$(git -C clone3/nested1/nested2 rev-parse HEAD)
+
+cat > expect <<EOF
+ $nested1sha1 ../nested1 (heads/master)
++$nested2sha1 ../nested1/nested2 (file2)
+ $nested3sha1 ../nested1/nested2/nested3 (heads/master)
+ $submodulesha1 ../nested1/nested2/nested3/submodule (heads/master)
+ $sub1sha1 ../sub1 ($sub1sha1_short)
+ $sub2sha1 ../sub2 ($sub2sha1_short)
+ $sub3sha1 ../sub3 (heads/master)
+EOF
+
+test_expect_success 'test "status --recursive" from sub directory' '
+       (
+               cd clone3 &&
+               mkdir tmp && cd tmp &&
+               git submodule status --recursive > ../../actual
+       ) &&
+       test_cmp expect actual
+'
+
 test_expect_success 'use "git clone --recursive" to checkout all submodules' '
        git clone --recursive super clone4 &&
        (
index 63e0427..900f7de 100755 (executable)
@@ -200,6 +200,26 @@ test_expect_success '--amend --edit of empty message' '
        test_cmp expect msg
 '
 
+test_expect_success '--amend to set message to empty' '
+       echo bata >file &&
+       git add file &&
+       git commit -m "unamended" &&
+       git commit --amend --allow-empty-message -m "" &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       echo "" >expect &&
+       test_cmp expect msg
+'
+
+test_expect_success '--amend to set empty message needs --allow-empty-message' '
+       echo conga >file &&
+       git add file &&
+       git commit -m "unamended" &&
+       test_must_fail git commit --amend -m "" &&
+       git diff-tree -s --format=%s HEAD >msg &&
+       echo "unamended" >expect &&
+       test_cmp expect msg
+'
+
 test_expect_success '-m --edit' '
        echo amended >expect &&
        git commit --allow-empty -m buffer &&
index 4e713f7..ff7a9e9 100755 (executable)
@@ -20,7 +20,7 @@ difftool_test_setup ()
 prompt_given ()
 {
        prompt="$1"
-       test "$prompt" = "Launch 'test-tool' [Y/n]: branch"
+       test "$prompt" = "Launch 'test-tool' [Y/n]? branch"
 }
 
 # Create a file on master and change it on branch
index 66d3fc9..eb9a8ed 100755 (executable)
@@ -223,12 +223,12 @@ build_gendouble() {
        import sys
        import struct
 
-       s = struct.pack(">LL18s",
+       s = struct.pack(b">LL18s",
                        0x00051607,  # AppleDouble
                        0x00020000,  # version 2
-                       ""           # pad to 26 bytes
+                       b""          # pad to 26 bytes
        )
-       sys.stdout.write(s)
+       getattr(sys.stdout, 'buffer', sys.stdout).write(s)
        EOF
 }
 
index 0b664a3..110a7e7 100755 (executable)
@@ -13,6 +13,10 @@ test_file_in_lfs () {
        FILE="$1" &&
        SIZE="$2" &&
        EXPECTED_CONTENT="$3" &&
+       sed -n '1,1 p' "$FILE" | grep "^version " &&
+       sed -n '2,2 p' "$FILE" | grep "^oid " &&
+       sed -n '3,3 p' "$FILE" | grep "^size " &&
+       test_line_count = 3 "$FILE" &&
        cat "$FILE" | grep "size $SIZE" &&
        HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") &&
        LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" &&
@@ -265,7 +269,7 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr
                # We only import HEAD here ("@all" is missing!)
                git p4 clone --destination="$git" //depot &&
 
-               test_file_in_lfs file6.bin 13 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ"
+               test_file_in_lfs file6.bin 39 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" &&
                test_file_count_in_dir ".git/lfs/objects" 1 &&
 
                cat >expect <<-\EOF &&
index 0b47eb6..79afa87 100644 (file)
@@ -202,13 +202,13 @@ do
                }
                run_list=$1; shift ;;
        --run=*)
-               run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;;
+               run_list=${1#--*=}; shift ;;
        -h|--h|--he|--hel|--help)
                help=t; shift ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
        --verbose-only=*)
-               verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               verbose_only=${1#--*=}
                shift ;;
        -q|--q|--qu|--qui|--quie|--quiet)
                # Ignore --quiet under a TAP::Harness. Saying how many tests
@@ -222,15 +222,15 @@ do
                valgrind=memcheck
                shift ;;
        --valgrind=*)
-               valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               valgrind=${1#--*=}
                shift ;;
        --valgrind-only=*)
-               valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               valgrind_only=${1#--*=}
                shift ;;
        --tee)
                shift ;; # was handled already
        --root=*)
-               root=$(expr "z$1" : 'z[^=]*=\(.*\)')
+               root=${1#--*=}
                shift ;;
        --chain-lint)
                GIT_TEST_CHAIN_LINT=1
index 9afc1a0..9009f8b 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -446,23 +446,6 @@ int git_mkstemp(char *path, size_t len, const char *template)
        return mkstemp(path);
 }
 
-/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */
-int git_mkstemps(char *path, size_t len, const char *template, int suffix_len)
-{
-       const char *tmp;
-       size_t n;
-
-       tmp = getenv("TMPDIR");
-       if (!tmp)
-               tmp = "/tmp";
-       n = snprintf(path, len, "%s/%s", tmp, template);
-       if (len <= n) {
-               errno = ENAMETOOLONG;
-               return -1;
-       }
-       return mkstemps(path, suffix_len);
-}
-
 /* Adapted from libiberty's mkstemp.c. */
 
 #undef TMP_MAX
index ef74864..1ea2ebe 100644 (file)
@@ -1063,9 +1063,7 @@ static void abbrev_sha1_in_line(struct strbuf *line)
                                strbuf_addf(line, "%s", split[i]->buf);
                }
        }
-       for (i = 0; split[i]; i++)
-               strbuf_release(split[i]);
-
+       strbuf_list_free(split);
 }
 
 static void read_rebase_todolist(const char *fname, struct string_list *lines)