Merge branch 'js/win32-mmap' into HEAD
authorJunio C Hamano <gitster@pobox.com>
Wed, 18 May 2016 21:40:06 +0000 (14:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 18 May 2016 21:40:06 +0000 (14:40 -0700)
mmap emulation on Windows has been optimized and work better without
consuming paging store when not needed.

* js/win32-mmap:
mmap(win32): avoid expensive fstat() call
mmap(win32): avoid copy-on-write when it is unnecessary
win32mmap: set errno appropriately

74 files changed:
Documentation/RelNotes/2.8.2.txt
Documentation/RelNotes/2.8.3.txt [new file with mode: 0644]
Documentation/diff-options.txt
Documentation/git-for-each-ref.txt
Documentation/git.txt
Documentation/technical/api-config.txt
Documentation/technical/api-trace.txt
GIT-VERSION-GEN
Makefile
RelNotes
abspath.c
branch.c
branch.h
builtin/apply.c
builtin/blame.c
builtin/branch.c
builtin/commit.c
builtin/init-db.c
builtin/mv.c
builtin/replace.c
builtin/send-pack.c
builtin/submodule--helper.c
cache.h
compat/apple-common-crypto.h
compat/mingw.c
compat/mingw.h
compat/snprintf.c
compat/vcbuild/include/unistd.h
compat/win32/pthread.h
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
diffcore-rename.c
environment.c
fetch-pack.c
git-compat-util.h
git-send-email.perl
git-submodule.sh
http.c
ident.c
imap-send.c
path.c
po/fr.po
refs.h
refs/files-backend.c
run-command.c
run-command.h
send-pack.c
setup.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/t4001-diff-rename.sh
t/t5400-send-pack.sh
t/t5504-fetch-receive-strict.sh
t/t5532-fetch-proxy.sh
t/t7001-mv.sh
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7501-commit.sh
t/t8003-blame-corner-cases.sh
t/t9802-git-p4-filetype.sh
index 3db67f4c55090545eb59b5c282589f040042aaca..447b1933a8f34874c933541e950e70ca181d8494 100644 (file)
@@ -52,4 +52,19 @@ Fixes since v2.8.1
    nothing into an unborn history (which is arguably unusual usage,
    which perhaps was the reason why nobody noticed it).
 
+ * Build updates for MSVC.
+
+ * "git diff -M" used to work better when two originally identical
+   files A and B got renamed to X/A and X/B by pairing A to X/A and B
+   to X/B, but this was broken in the 2.0 timeframe.
+
+ * "git send-pack --all <there>" was broken when its command line
+   option parsing was written in the 2.6 timeframe.
+
+ * When running "git blame $path" with unnormalized data in the index
+   for the path, the data in the working tree was blamed, even though
+   "git add" would not have changed what is already in the index, due
+   to "safe crlf" that disables the line-end conversion.  It has been
+   corrected.
+
 Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.8.3.txt b/Documentation/RelNotes/2.8.3.txt
new file mode 100644 (file)
index 0000000..af18478
--- /dev/null
@@ -0,0 +1,70 @@
+Git v2.8.3 Release Notes
+========================
+
+Fixes since v2.8.2
+------------------
+
+ * "git send-email" now uses a more readable timestamps when
+   formulating a message ID.
+
+ * The repository set-up sequence has been streamlined (the biggest
+   change is that there is no longer git_config_early()), so that we
+   do not attempt to look into refs/* when we know we do not have a
+   Git repository.
+
+ * When "git worktree" feature is in use, "git branch -d" allowed
+   deletion of a branch that is checked out in another worktree
+
+ * When "git worktree" feature is in use, "git branch -m" renamed a
+   branch that is checked out in another worktree without adjusting
+   the HEAD symbolic ref for the worktree.
+
+ * "git format-patch --help" showed `-s` and `--no-patch` as if these
+   are valid options to the command.  We already hide `--patch` option
+   from the documentation, because format-patch is about showing the
+   diff, and the documentation now hides these options as well.
+
+ * A change back in version 2.7 to "git branch" broke display of a
+   symbolic ref in a non-standard place in the refs/ hierarchy (we
+   expect symbolic refs to appear in refs/remotes/*/HEAD to point at
+   the primary branch the remote has, and as .git/HEAD to point at the
+   branch we locally checked out).
+
+ * A partial rewrite of "git submodule" in the 2.7 timeframe changed
+   the way the gitdir: pointer in the submodules point at the real
+   repository location to use absolute paths by accident.  This has
+   been corrected.
+
+ * "git commit" misbehaved in a few minor ways when an empty message
+   is given via -m '', all of which has been corrected.
+
+ * Support for CRAM-MD5 authentication method in "git imap-send" did
+   not work well.
+
+ * The socks5:// proxy support added back in 2.6.4 days was not aware
+   that socks5h:// proxies behave differently.
+
+ * "git config" had a codepath that tried to pass a NULL to
+   printf("%s"), which nobody seems to have noticed.
+
+ * On Cygwin, object creation uses the "create a temporary and then
+   rename it to the final name" pattern, not "create a temporary,
+   hardlink it to the final name and then unlink the temporary"
+   pattern.
+
+   This is necessary to use Git on Windows shared directories, and is
+   already enabled for the MinGW and plain Windows builds.  It also
+   has been used in Cygwin packaged versions of Git for quite a while.
+   See http://thread.gmane.org/gmane.comp.version-control.git/291853
+   and http://thread.gmane.org/gmane.comp.version-control.git/275680.
+
+ * "git replace -e" did not honour "core.editor" configuration.
+
+ * Upcoming OpenSSL 1.1.0 will break compilation b updating a few APIs
+   we use in imap-send, which has been adjusted for the change.
+
+ * "git submodule" reports the paths of submodules the command
+   recurses into, but this was incorrect when the command was not run
+   from the root level of the superproject.
+
+Also contains minor documentation updates and code clean-ups.
index 32f48ed6471946aa8baf2d51113834804cd09b47..4b0318e2ac159eb56ff4fbafe2f3c2ebfa374392 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 012e8f9a080d2dc386d2f878c2bc9812ba6113f8..c52578bb87ccfec1cf37defd4c3be4afbb16259b 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 8afe349781d57527083fdb75511959fd25a4239b..34ff007a98156162431d5dd03abaaffe5d0c29a0 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v2.8.1/git.html[documentation for release 2.8.1]
+* link:v2.8.2/git.html[documentation for release 2.8.2]
 
 * release notes for
+  link:RelNotes/2.8.2.txt[2.8.2].
   link:RelNotes/2.8.1.txt[2.8.1].
   link:RelNotes/2.8.0.txt[2.8].
 
index 0d8b99b368aea13a322382314d88229acda0b1db..20741f345e459f41d357039496f7d6be30edab06 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 389ae16d150f9eca5f4f137739dfb55e742f17dd..fadb5979c48b6c128f71db1588412bf0a8204677 100644 (file)
@@ -28,7 +28,7 @@ static struct trace_key trace_foo = TRACE_KEY_INIT(FOO);
 
 static void trace_print_foo(const char *message)
 {
-       trace_print_key(&trace_foo, message);
+       trace_printf_key(&trace_foo, "%s", message);
 }
 ------------
 +
index 46595dad2234f861198347ef8f4f60d167061709..5f99f23cadea4d17d88331871fa428a0c50c9811 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.8.1
+DEF_VER=v2.8.2
 
 LF='
 '
index 2742a6977c6ad871897bc758ea2a7d76359b1eee..a83e322f468a2bd3cad4058a7dffffbda822033f 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
@@ -2263,10 +2257,10 @@ sparse: $(SP_OBJ)
 check: common-cmds.h
        @if sparse; \
        then \
-               echo 2>&1 "Use 'make sparse' instead"; \
+               echo >&2 "Use 'make sparse' instead"; \
                $(MAKE) --no-print-directory sparse; \
        else \
-               echo 2>&1 "Did you mean 'make test'?"; \
+               echo >&2 "Did you mean 'make test'?"; \
                exit 1; \
        fi
 
index d40c3e126c03b0e4bd9c6162f63a35a45f5e9020..04710daa9567fb905c8213deb8634558d9d0a7aa 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.8.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.8.2.txt
\ No newline at end of file
index 5edb4e78162ca6646ef42ad2c8abc36872cd75a5..2825de85912fc730d9a40fa66f4f83b6250a4171 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -167,7 +167,6 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
        strbuf_add(&path, pfx, pfx_len);
        strbuf_addstr(&path, arg);
 #else
-       char *p;
        /* don't add prefix to absolute paths, but still replace '\' by '/' */
        strbuf_reset(&path);
        if (is_absolute_path(arg))
@@ -175,9 +174,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg)
        else if (pfx_len)
                strbuf_add(&path, pfx, pfx_len);
        strbuf_addstr(&path, arg);
-       for (p = path.buf + pfx_len; *p; p++)
-               if (*p == '\\')
-                       *p = '/';
+       convert_slashes(path.buf + pfx_len);
 #endif
        return path.buf;
 }
index c50ea42172ceadd2a76d12833631301223607067..416244370783adcd7648ae7c03c7d0d8cf778b12 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 78ad4387cd326ca01f228b109b446e2049f29ee7..d69163daf793f92f3dab92e2fb9bc5217b2dfd6b 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 42c610e2ec180e789fdae4bc637dd96f533d1e10..ce3b77853c7a409b40d14400172327978266f9ca 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 e982fb81379f57152e34eeda706a57fa1ea4c143..21f42b0b62b81b637f1cc9589cd6c0306a93d05e 100644 (file)
@@ -2307,6 +2307,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        unsigned mode;
        struct strbuf msg = STRBUF_INIT;
 
+       read_cache();
        time(&now);
        commit = alloc_commit_node();
        commit->object.parsed = 1;
index 7b45b6bd6b80613de9f894cfbe8e9d2dcae77b09..0adba629d2ae35a4db74fb9d1450b54bb11e51e3 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 c733ec98b7057b6310db6b364d5780170455dbaa..e13303787a13aecf282ecd6b207f7f0c2a855cde 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 da531f6b766fb300d8c1fff0d94a0756bbbf1f6f..b2d8d40a6750e5794dd9bfbfbcb31924a7eec4ba 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 aeae855e2b95399db4ae6032d118848449488267..a2014266b6b33d7940627777af9ae860c7850c2d 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 748c6ca954679e3fc47c25991dd50e539eaa543d..b58c714cb8935efd08c81c8ebfd8922e1704c8f4 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 5b9dd6a9d8ebe888c2fc912af3590b92737e7a99..1ff5a6753803f8c2ccb5e66dcb926582bd25fd37 100644 (file)
@@ -225,7 +225,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
         * --all and --mirror are incompatible; neither makes sense
         * with any refspecs.
         */
-       if ((refspecs && (send_all || args.send_mirror)) ||
+       if ((nr_refspecs > 0 && (send_all || args.send_mirror)) ||
            (send_all && args.send_mirror))
                usage_with_options(send_pack_usage, options);
 
index 5295b727d4609fa33a08c31b2d051ce3f98244fb..3bea3aaa5029082198abab2b5a5dab838dbd6dec 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;
 }
diff --git a/cache.h b/cache.h
index 9f09540bbca587cb2ba0135a69d65aa966b09622..2711048cad7d5c7bba04c5737aa6142caf49d023 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
@@ -1526,7 +1558,6 @@ extern void git_config(config_fn_t fn, void *);
 extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
                                   int respect_includes);
-extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_parse_maybe_bool(const char *);
 extern int git_config_int(const char *, const char *);
@@ -1550,7 +1581,6 @@ extern void git_config_set_multivar_in_file(const char *, const char *, const ch
 extern int git_config_rename_section(const char *, const char *);
 extern int git_config_rename_section_in_file(const char *, const char *, const char *);
 extern const char *git_etc_gitconfig(void);
-extern int check_repository_format_version(const char *var, const char *value, void *cb);
 extern int git_env_bool(const char *, int);
 extern unsigned long git_env_ulong(const char *, unsigned long);
 extern int git_config_system(void);
index d3fb2641813404a95709d2b219b9ab1fa1434296..11727f3e1ed7f6d51e334734c19e9eafbf034783 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 54c82ecf201dde2c317efe6862173c1b40f2552d..0413d5c3cdd128ca721c75b588dc557bdbb1f949 100644 (file)
@@ -763,15 +763,12 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
 
 char *mingw_getcwd(char *pointer, int len)
 {
-       int i;
        wchar_t wpointer[MAX_PATH];
        if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
                return NULL;
        if (xwcstoutf(pointer, wpointer, len) < 0)
                return NULL;
-       for (i = 0; pointer[i]; i++)
-               if (pointer[i] == '\\')
-                       pointer[i] = '/';
+       convert_slashes(pointer);
        return pointer;
 }
 
@@ -2112,9 +2109,7 @@ static void setup_windows_environment()
                 * executable (by not mistaking the dir separators
                 * for escape characters).
                 */
-               for (; *tmp; tmp++)
-                       if (*tmp == '\\')
-                               *tmp = '/';
+               convert_slashes(tmp);
        }
 
        /* simulate TERM to enable auto-color (see color.c) */
index c008694639a654e6a04e541af70e90a5fa4da9e0..edec9e0253560445db6863ab3ebdd7307e486339 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; }
@@ -406,7 +407,7 @@ static inline void convert_slashes(char *path)
 int mingw_offset_1st_component(const char *path);
 #define offset_1st_component mingw_offset_1st_component
 #define PATH_SEP ';'
-#ifndef __MINGW64_VERSION_MAJOR
+#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
 #define PRIuMAX "I64u"
 #define PRId64 "I64d"
 #else
index 42ea1ac110813bbd16e77cfbc36f16e6a5e9ddb2..0b1168853778810d385c84625ad643587537913b 100644 (file)
@@ -9,7 +9,7 @@
  * always have room for a trailing NUL byte.
  */
 #ifndef SNPRINTF_SIZE_CORR
-#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900)
 #define SNPRINTF_SIZE_CORR 1
 #else
 #define SNPRINTF_SIZE_CORR 0
index c65c2cd566b51473da12786f28074040c410f343..3a959d124ca794446800653e9c033b883d8d7b8f 100644 (file)
@@ -45,11 +45,15 @@ typedef unsigned long long uintmax_t;
 
 typedef int64_t off64_t;
 
+#if !defined(_MSC_VER) || _MSC_VER < 1600
 #define INTMAX_MIN  _I64_MIN
 #define INTMAX_MAX  _I64_MAX
 #define UINTMAX_MAX _UI64_MAX
 
 #define UINT32_MAX 0xffffffff  /* 4294967295U */
+#else
+#include <stdint.h>
+#endif
 
 #define STDIN_FILENO  0
 #define STDOUT_FILENO 1
index b6ed9e74621fe0bc506beb893e477bff26e88756..1c164088fbb64d2f0143f536f3186f481d876d28 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 9ba40bc1b039b9b65425dc4fa1bd9c7f1fcb0868..47b2ce1d5a753f11bb1876e709e6a7324aa47e54 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)
@@ -2221,9 +2217,13 @@ void git_config_set_multivar_in_file(const char *config_filename,
                                     const char *key, const char *value,
                                     const char *value_regex, int multi_replace)
 {
-       if (git_config_set_multivar_in_file_gently(config_filename, key, value,
-                                                  value_regex, multi_replace) < 0)
-               die(_("Could not set '%s' to '%s'"), key, value);
+       if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
+                                                   value_regex, multi_replace))
+               return;
+       if (value)
+               die(_("could not set '%s' to '%s'"), key, value);
+       else
+               die(_("could not unset '%s'"), key);
 }
 
 int git_config_set_multivar_gently(const char *key, const char *value,
@@ -2404,7 +2404,7 @@ int git_config_rename_section(const char *old_name, const char *new_name)
 #undef config_error_nonbool
 int config_error_nonbool(const char *var)
 {
-       return error("Missing value for '%s'", var);
+       return error("missing value for '%s'", var);
 }
 
 int parse_config_key(const char *var,
index fe8096f8a65241760cfdc3dabd4cf63ee412e9ca..40d6b29eeef9a9b89b02100bb087fe9f37a3a7d6 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 0cd9f4680b84bccd0a98193c401b13749b05b833..c279025747349c19039b0e061954ae67b91b735a 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 bc77e66b8555679ed479929f2e756152cdee19fc..53c71b422a7ac26511b76bc3854e1f04918096f6 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 09efdb059c4ef6caf7524eab8eebe4e673ca6c50..530ecbfcf1f688bb81f36550bd6d96ecba165457 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 55120685f04674daed7c0c32b8c9f33b0c8b79e3..1e0480197843ee7e3a5a9152f98dcae8f3f56f14 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 300a2a4d2d479b3ab002a5e6480c3e9a83e36fb7..ee1fa75f99f193bbb8f05b343b99f2c776aa1084 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 0180dba43126df209bde82e3e5aea3616f402c96..f2c92aeed8e45545887197e7e4dfbda1b366153c 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
 import shlex
 import optparse
 import smtplib
+try:
+    import ssl
+except ImportError:
+    # Python < 2.6 do not have ssl, but that's OK if we don't use it.
+    pass
 import time
 import cgi
 
@@ -75,6 +80,9 @@ def is_ascii(s):
 
 
 if PYTHON3:
+    def is_string(s):
+        return isinstance(s, str)
+
     def str_to_bytes(s):
         return s.encode(ENCODING)
 
@@ -91,6 +99,12 @@ def write_str(f, msg):
         except UnicodeEncodeError:
             f.buffer.write(msg.encode(ENCODING))
 else:
+    def is_string(s):
+        try:
+            return isinstance(s, basestring)
+        except NameError:  # Silence Pyflakes warning
+            raise
+
     def str_to_bytes(s):
         return s
 
@@ -313,6 +327,16 @@ def next(it):
 
 """
 
+LINK_TEXT_TEMPLATE = """\
+View the commit online:
+%(browse_url)s
+
+"""
+
+LINK_HTML_TEMPLATE = """\
+<p><a href="%(browse_url)s">View the commit online</a>.</p>
+"""
+
 
 REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
 
@@ -532,6 +556,28 @@ def _split(s):
         assert words[-1] == ''
         return words[:-1]
 
+    @staticmethod
+    def add_config_parameters(c):
+        """Add configuration parameters to Git.
+
+        c is either an str or a list of str, each element being of the
+        form 'var=val' or 'var', with the same syntax and meaning as
+        the argument of 'git -c var=val'.
+        """
+        if isinstance(c, str):
+            c = (c,)
+        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+        if parameters:
+            parameters += ' '
+        # git expects GIT_CONFIG_PARAMETERS to be of the form
+        #    "'name1=value1' 'name2=value2' 'name3=value3'"
+        # including everything inside the double quotes (but not the double
+        # quotes themselves).  Spacing is critical.  Also, if a value contains
+        # a literal single quote that quote must be represented using the
+        # four character sequence: '\''
+        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c)
+        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+
     def get(self, name, default=None):
         try:
             values = self._split(read_git_output(
@@ -745,6 +791,12 @@ def _compute_values(self):
         values['multimail_version'] = get_version()
         return values
 
+    # Aliases usable in template strings. Tuple of pairs (destination,
+    # source).
+    VALUES_ALIAS = (
+        ("id", "newrev"),
+        )
+
     def get_values(self, **extra_values):
         """Return a dictionary {keyword: expansion} for this Change.
 
@@ -760,6 +812,9 @@ def get_values(self, **extra_values):
         values = self._values.copy()
         if extra_values:
             values.update(extra_values)
+
+        for alias, val in self.VALUES_ALIAS:
+            values[alias] = values[val]
         return values
 
     def expand(self, template, **extra_values):
@@ -772,10 +827,14 @@ def expand(self, template, **extra_values):
 
         return template % self.get_values(**extra_values)
 
-    def expand_lines(self, template, **extra_values):
+    def expand_lines(self, template, html_escape_val=False, **extra_values):
         """Break template into lines and expand each line."""
 
         values = self.get_values(**extra_values)
+        if html_escape_val:
+            for k in values:
+                if is_string(values[k]):
+                    values[k] = cgi.escape(values[k], True)
         for line in template.splitlines(True):
             yield line % values
 
@@ -787,9 +846,10 @@ def expand_header_lines(self, template, **extra_values):
 
         values = self.get_values(**extra_values)
         if self._contains_html_diff:
-            values['contenttype'] = 'html'
+            self._content_type = 'html'
         else:
-            values['contenttype'] = 'plain'
+            self._content_type = 'plain'
+        values['contenttype'] = self._content_type
 
         for line in template.splitlines():
             (name, value) = line.split(': ', 1)
@@ -819,7 +879,11 @@ def generate_email_header(self):
 
         raise NotImplementedError()
 
-    def generate_email_intro(self):
+    def generate_browse_link(self, base_url):
+        """Generate a link to an online repository browser."""
+        return iter(())
+
+    def generate_email_intro(self, html_escape_val=False):
         """Generate the email intro for this Change, a line at a time.
 
         The output will be used as the standard boilerplate at the top
@@ -835,7 +899,7 @@ def generate_email_body(self):
 
         raise NotImplementedError()
 
-    def generate_email_footer(self):
+    def generate_email_footer(self, html_escape_val):
         """Generate the footer of the email, a line at a time.
 
         The footer is always included, irrespective of
@@ -876,9 +940,18 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
         for line in self.generate_email_header(**extra_header_values):
             yield line
         yield '\n'
-        for line in self._wrap_for_html(self.generate_email_intro()):
+        html_escape_val = (self.environment.html_in_intro and
+                           self._contains_html_diff)
+        intro = self.generate_email_intro(html_escape_val)
+        if not self.environment.html_in_intro:
+            intro = self._wrap_for_html(intro)
+        for line in intro:
             yield line
 
+        if self.environment.commitBrowseURL:
+            for line in self.generate_browse_link(self.environment.commitBrowseURL):
+                yield line
+
         body = self.generate_email_body(push)
         if body_filter is not None:
             body = body_filter(body)
@@ -939,8 +1012,12 @@ def generate_email(self, push, body_filter=None, extra_header_values={}):
             yield line
         if self._contains_html_diff:
             yield '</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 @@ def _compute_values(self):
         values['rev_short'] = self.rev.short
         values['change_type'] = self.change_type
         values['refname'] = self.refname
+        values['newrev'] = self.rev.sha1
         values['short_refname'] = self.reference_change.short_refname
         values['refname_type'] = self.reference_change.refname_type
         values['reply_to_msgid'] = self.reference_change.msgid
@@ -1015,8 +1093,26 @@ def generate_email_header(self, **extra_values):
                 ):
             yield line
 
-    def generate_email_intro(self):
-        for line in self.expand_lines(REVISION_INTRO_TEMPLATE):
+    def generate_browse_link(self, base_url):
+        if '%(' not in base_url:
+            base_url += '%(id)s'
+        url = "".join(self.expand_lines(base_url))
+        if self._content_type == 'html':
+            for line in self.expand_lines(LINK_HTML_TEMPLATE,
+                                          html_escape_val=True,
+                                          browse_url=url):
+                yield line
+        elif self._content_type == 'plain':
+            for line in self.expand_lines(LINK_TEXT_TEMPLATE,
+                                          html_escape_val=False,
+                                          browse_url=url):
+                yield line
+        else:
+            raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.")
+
+    def generate_email_intro(self, html_escape_val=False):
+        for line in self.expand_lines(REVISION_INTRO_TEMPLATE,
+                                      html_escape_val=html_escape_val):
             yield line
 
     def generate_email_body(self, push):
@@ -1031,8 +1127,9 @@ def generate_email_body(self, push):
             else:
                 yield line
 
-    def generate_email_footer(self):
-        return self.expand_lines(REVISION_FOOTER_TEMPLATE)
+    def generate_email_footer(self, html_escape_val):
+        return self.expand_lines(REVISION_FOOTER_TEMPLATE,
+                                 html_escape_val=html_escape_val)
 
     def generate_email(self, push, body_filter=None, extra_header_values={}):
         self._contains_diff()
@@ -1217,8 +1314,9 @@ def generate_email_header(self, **extra_values):
                 ):
             yield line
 
-    def generate_email_intro(self):
-        for line in self.expand_lines(self.intro_template):
+    def generate_email_intro(self, html_escape_val=False):
+        for line in self.expand_lines(self.intro_template,
+                                      html_escape_val=html_escape_val):
             yield line
 
     def generate_email_body(self, push):
@@ -1238,8 +1336,9 @@ def generate_email_body(self, push):
         for line in self.generate_revision_change_summary(push):
             yield line
 
-    def generate_email_footer(self):
-        return self.expand_lines(self.footer_template)
+    def generate_email_footer(self, html_escape_val):
+        return self.expand_lines(self.footer_template,
+                                 html_escape_val=html_escape_val)
 
     def generate_revision_change_graph(self, push):
         if self.showgraph:
@@ -1896,6 +1995,7 @@ def __init__(self, envelopesender, smtpserver,
                  smtpservertimeout=10.0, smtpserverdebuglevel=0,
                  smtpencryption='none',
                  smtpuser='', smtppass='',
+                 smtpcacerts=''
                  ):
         if not envelopesender:
             sys.stderr.write(
@@ -1915,6 +2015,7 @@ def __init__(self, envelopesender, smtpserver,
         self.security = smtpencryption
         self.username = smtpuser
         self.password = smtppass
+        self.smtpcacerts = smtpcacerts
         try:
             def call(klass, server, timeout):
                 try:
@@ -1925,13 +2026,56 @@ def call(klass, server, timeout):
             if self.security == 'none':
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'ssl':
+                if self.smtpcacerts:
+                    raise smtplib.SMTPException(
+                        "Checking certificate is not supported for ssl, prefer starttls"
+                        )
                 self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
             elif self.security == 'tls':
+                if 'ssl' not in sys.modules:
+                    sys.stderr.write(
+                        '*** Your Python version does not have the ssl library installed\n'
+                        '*** smtpEncryption=tls is not available.\n'
+                        '*** Either upgrade Python to 2.6 or later\n'
+                        '    or use git_multimail.py version 1.2.\n')
                 if ':' not in self.smtpserver:
                     self.smtpserver += ':587'  # default port for TLS
                 self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+                # start: ehlo + starttls
+                # equivalent to
+                #     self.smtp.ehlo()
+                #     self.smtp.starttls()
+                # with acces to the ssl layer
                 self.smtp.ehlo()
-                self.smtp.starttls()
+                if not self.smtp.has_extn("starttls"):
+                    raise smtplib.SMTPException("STARTTLS extension not supported by server")
+                resp, reply = self.smtp.docmd("STARTTLS")
+                if resp != 220:
+                    raise smtplib.SMTPException("Wrong answer to the STARTTLS command")
+                if self.smtpcacerts:
+                    self.smtp.sock = ssl.wrap_socket(
+                        self.smtp.sock,
+                        ca_certs=self.smtpcacerts,
+                        cert_reqs=ssl.CERT_REQUIRED
+                        )
+                else:
+                    self.smtp.sock = ssl.wrap_socket(
+                        self.smtp.sock,
+                        cert_reqs=ssl.CERT_NONE
+                        )
+                    sys.stderr.write(
+                        '*** Warning, the server certificat is not verified (smtp) ***\n'
+                        '***          set the option smtpCACerts                   ***\n'
+                        )
+                if not hasattr(self.smtp.sock, "read"):
+                    # using httplib.FakeSocket with Python 2.5.x or earlier
+                    self.smtp.sock.read = self.smtp.sock.recv
+                self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock)
+                self.smtp.helo_resp = None
+                self.smtp.ehlo_resp = None
+                self.smtp.esmtp_features = {}
+                self.smtp.does_esmtp = 0
+                # end:   ehlo + starttls
                 self.smtp.ehlo()
             else:
                 sys.stdout.write('*** Error: Control reached an invalid option. ***')
@@ -1951,6 +2095,7 @@ def call(klass, server, timeout):
     def __del__(self):
         if hasattr(self, 'smtp'):
             self.smtp.quit()
+            del self.smtp
 
     def send(self, lines, to_addrs):
         try:
@@ -1958,13 +2103,24 @@ def send(self, lines, to_addrs):
                 self.smtp.login(self.username, self.password)
             msg = ''.join(lines)
             # turn comma-separated list into Python list if needed.
-            if isinstance(to_addrs, basestring):
+            if is_string(to_addrs):
                 to_addrs = [email for (name, email) in getaddresses([to_addrs])]
             self.smtp.sendmail(self.envelopesender, to_addrs, msg)
-        except Exception:
+        except smtplib.SMTPResponseException:
             sys.stderr.write('*** Error sending email ***\n')
-            sys.stderr.write('*** %s\n' % sys.exc_info()[1])
-            self.smtp.quit()
+            err = sys.exc_info()[1]
+            sys.stderr.write('*** Error %d: %s\n' % (err.smtp_code,
+                                                     bytes_to_str(err.smtp_error)))
+            try:
+                smtp = self.smtp
+                # delete the field before quit() so that in case of
+                # error, self.smtp is deleted anyway.
+                del self.smtp
+                smtp.quit()
+            except:
+                sys.stderr.write('*** Error closing the SMTP connection ***\n')
+                sys.stderr.write('*** Exiting anyway ... ***\n')
+                sys.stderr.write('*** %s\n' % sys.exc_info()[1])
             sys.exit(1)
 
 
@@ -2097,6 +2253,14 @@ class Environment(object):
             If "html", generate commit emails in HTML instead of plain text
             used by default.
 
+        html_in_intro (bool)
+        html_in_footer (bool)
+
+            When generating HTML emails, the introduction (respectively,
+            the footer) will be HTML-escaped iff html_in_intro (respectively,
+            the footer) is true. When false, only the values used to expand
+            the template are escaped.
+
         refchange_showgraph (bool)
 
             True iff refchanges emails should include a detailed graph.
@@ -2160,6 +2324,9 @@ def __init__(self, osenv=None):
         self.osenv = osenv or os.environ
         self.announce_show_shortlog = False
         self.commit_email_format = "text"
+        self.html_in_intro = False
+        self.html_in_footer = False
+        self.commitBrowseURL = None
         self.maxcommitemails = 500
         self.diffopts = ['--stat', '--summary', '--find-copies-harder']
         self.graphopts = ['--oneline', '--decorate']
@@ -2236,7 +2403,7 @@ def get_values(self):
         The return value is always a new dictionary."""
 
         if self._values is None:
-            values = {}
+            values = {'': ''}  # %()s expands to the empty string.
 
             for key in self.COMPUTED_KEYS:
                 value = getattr(self, 'get_%s' % (key,))()
@@ -2375,6 +2542,16 @@ def __init__(self, config, **kw):
             else:
                 self.commit_email_format = commit_email_format
 
+        html_in_intro = config.get_bool('htmlInIntro')
+        if html_in_intro is not None:
+            self.html_in_intro = html_in_intro
+
+        html_in_footer = config.get_bool('htmlInFooter')
+        if html_in_footer is not None:
+            self.html_in_footer = html_in_footer
+
+        self.commitBrowseURL = config.get('commitBrowseURL')
+
         maxcommitemails = config.get('maxcommitemails')
         if maxcommitemails is not None:
             try:
@@ -2415,7 +2592,6 @@ def __init__(self, config, **kw):
                                  ['author'])
         self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
 
-        from_addr = self.config.get('from')
         self.from_refchange = config.get('fromRefchange')
         self.forbid_field_values('fromRefchange',
                                  self.from_refchange,
@@ -3390,6 +3566,8 @@ def run_as_post_receive_hook(environment, mailer):
     if changes:
         push = Push(environment, changes)
         push.send_emails(mailer, body_filter=environment.filter_body)
+    if hasattr(mailer, '__del__'):
+        mailer.__del__()
 
 
 def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
@@ -3406,6 +3584,8 @@ def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=
         ]
     push = Push(environment, changes, force_send)
     push.send_emails(mailer, body_filter=environment.filter_body)
+    if hasattr(mailer, '__del__'):
+        mailer.__del__()
 
 
 def choose_mailer(config, environment):
@@ -3418,6 +3598,7 @@ def choose_mailer(config, environment):
         smtpencryption = config.get('smtpencryption', default='none')
         smtpuser = config.get('smtpuser', default='')
         smtppass = config.get('smtppass', default='')
+        smtpcacerts = config.get('smtpcacerts', default='')
         mailer = SMTPMailer(
             envelopesender=(environment.get_sender() or environment.get_fromaddr()),
             smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
@@ -3425,6 +3606,7 @@ def choose_mailer(config, environment):
             smtpencryption=smtpencryption,
             smtpuser=smtpuser,
             smtppass=smtppass,
+            smtpcacerts=smtpcacerts
             )
     elif mailer == 'sendmail':
         command = config.get('sendmailcommand')
@@ -3691,17 +3873,7 @@ def main(args):
         return
 
     if options.c:
-        parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
-        if parameters:
-            parameters += ' '
-        # git expects GIT_CONFIG_PARAMETERS to be of the form
-        #    "'name1=value1' 'name2=value2' 'name3=value3'"
-        # including everything inside the double quotes (but not the double
-        # quotes themselves).  Spacing is critical.  Also, if a value contains
-        # a literal single quote that quote must be represented using the
-        # four character sequence: '\''
-        parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c)
-        os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+        Config.add_config_parameters(options.c)
 
     config = Config('multimailhook')
 
index 9975df7107ac29be2b2ac861b90dfc0730ec7b97..1ea113d274e27adfede78bd5b5530608a47fa378 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 3b3c1ed535e7c8b17947d69dbe0bd5c8f115cf2d..7f03eb5a0404d4b44f292fba76433dfec74dcd44 100644 (file)
@@ -340,9 +340,11 @@ static int find_exact_renames(struct diff_options *options)
        int i, renames = 0;
        struct hashmap file_table;
 
-       /* Add all sources to the hash table */
+       /* Add all sources to the hash table in reverse order, because
+        * later on they will be retrieved in LIFO order.
+        */
        hashmap_init(&file_table, NULL, rename_src_nr);
-       for (i = 0; i < rename_src_nr; i++)
+       for (i = rename_src_nr-1; i >= 0; i--)
                insert_file_table(&file_table, i, rename_src[i].p->one);
 
        /* Walk the destinations and find best source match */
index 6cc0a7780f790d6cea3774acd989a57e52934f91..57acb2fe2aee79a30c4c956dcececd6a7122aeb6 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 f96f6dfb35afb419ac38fcec9b4013fbf0e6d36d..b501d5c320a5117020a934a50fce2a356b0c2f0e 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 474395471f623d6a02de9814a5490f3b1df3a0f1..1f8b5f3b1f1ac17716681fee2d72b9c124a9b99c 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 c45b22a19a06234d1106eedfeb8f80d29c37370e..1406f64d780a481cfc8be0e09a66f0c76887badd 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);
@@ -949,7 +949,7 @@ sub validate_address_list {
 sub make_message_id {
        my $uniq;
        if (!defined $message_id_stamp) {
-               $message_id_stamp = sprintf("%s-%s", time, $$);
+               $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time));
                $message_id_serial = 0;
        }
        $message_id_serial++;
@@ -964,7 +964,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 43c68deee9db2b243d39eba704878eb7551ada2d..753a90d3071d1917495ca725fe15a944f84b6f60 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
                        ) ||
diff --git a/http.c b/http.c
index 69da4454d8f754598d0316d0e1cb34870aba2b8e..4304b80ad3ac9d8ae249bc0bc007074bc5aa6181 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 6e125821f0563c46eaf8253f31e19d9d97a94e7e..4fd82d104365c2e2c1ab7e1597e7e246c8eded24 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 2c52027c84455819740bed9f53970e175b9ca133..938c6915858b93b7c860e49e906c45e0e2ea5d03 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 969b494d72dff1439150e7bb945339be07c4d475..bbaea5ab0bf73056dfa74063474f62685fda1d5e 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;
index 88b0b8a78ad7da5dd488a015c450455d94ce3eca..55ca387ba4bf3fc174baf4cd72e70455180cf05b 100644 (file)
--- a/po/fr.po
+++ b/po/fr.po
@@ -2945,7 +2945,7 @@ msgstr "utiliser l'horodatage actuel pour la date d'auteur"
 #: builtin/am.c:2321 builtin/commit.c:1593 builtin/merge.c:225
 #: builtin/pull.c:159 builtin/revert.c:92 builtin/tag.c:355
 msgid "key-id"
-msgstr "id de clé"
+msgstr "id-clé"
 
 #: builtin/am.c:2322
 msgid "GPG-sign commits"
@@ -4545,7 +4545,7 @@ msgstr "style"
 
 #: builtin/checkout.c:1154
 msgid "conflict style (merge or diff3)"
-msgstr "style de conflit (fusion ou diff3)"
+msgstr "style de conflit (merge (fusion) ou diff3)"
 
 #: builtin/checkout.c:1157
 msgid "do not limit pathspecs to sparse entries only"
@@ -6197,7 +6197,7 @@ msgstr "convertir en un dépôt complet"
 
 #: builtin/fetch.c:122 builtin/log.c:1236
 msgid "dir"
-msgstr "dir"
+msgstr "répertoire"
 
 #: builtin/fetch.c:123
 msgid "prepend this to submodule path output"
@@ -10809,11 +10809,11 @@ msgstr "git show-ref --exclude-existing[=<motif>]"
 
 #: builtin/show-ref.c:165
 msgid "only show tags (can be combined with heads)"
-msgstr "afficher seulement les Ã©tiquettes (peut Ãªtre combiné avec des têtes)"
+msgstr "afficher seulement les Ã©tiquettes (peut Ãªtre combiné avec heads)"
 
 #: builtin/show-ref.c:166
 msgid "only show heads (can be combined with tags)"
-msgstr "afficher seulement les têtes (peut Ãªtre combiné avec des Ã©tiquettes)"
+msgstr "afficher seulement les têtes (peut Ãªtre combiné avec tags)"
 
 #: builtin/show-ref.c:167
 msgid "stricter reference checking, requires exact ref path"
diff --git a/refs.h b/refs.h
index 2f3decb432cfeda97fd5da7f16dd4e5b725b59bc..9230d4714205f810c7c749559a20e1379197043b 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 81f68f846b69af65badfbe8c25d4a03601fb23c8..ea78ce9d90f0bb02fcd471c1c113e22053a3ba94 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 c72601056cf5ae7be2593ae89af4effc26a1b043..2d6628012d7f3dc555d9bf385e990ac2c4ec6885 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 3d1e59e26e33d062a10698fc139f7fc0f4ae14ec..49ba764c6c1d6b6d14e2e1cd56f3a5b6e8a37c12 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 047bd18fde3d09921902ebb656ab78bfbcf940a8..37ee04ea3bf41b3bc4b0b920d52449c2f6d681d7 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 3439ec6d815f823ed853251c9afa94d14d78556a..c86bf5c9fabeab4b60b0696b9ae9b2f0f2bb720e 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 92502b594d055bbf99fd203175d7dffaf8dfb732..8476e0fa0f89c968e95182fd44211c0018a4b1f8 100644 (file)
@@ -392,8 +392,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,
                struct hashmap_iter iter;
                struct submodule_entry *entry;
 
-               hashmap_iter_init(&cache->for_name, &iter);
-               entry = hashmap_iter_next(&iter);
+               entry = hashmap_iter_first(&cache->for_name, &iter);
                if (!entry)
                        return NULL;
                return entry->config;
index f9ae1d780dceb526fcfbce2684e3e7dc9b06f5eb..012d40e74695f8c195236a22802fd3ca6d60a8d8 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 8e22b03cdd132bd54f9db44d362c15d129415651..df3183ea1ab36a46b914333e834093bd50847265 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 a897248490650ebef1a33a60a1aa9cf4431f06ff..f3e3b6cf2eabf0d57f64d794dba9a6195e8da246 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 4261403cf62542e82d40487e47733f586e106a75..c6a3ccba1b992cff7412bcf8139da7557981de94 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 544f9ad50831e29095a9f143bf414e08fed46d9e..d6d65a3a9435a456f8efad15b8c7c63db00a8e5f 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 2f327b749588af7eda57187cba4d365233a25ef9..ed90c6c6f984979bfc250d277486550cb5129e30 100755 (executable)
@@ -77,6 +77,17 @@ test_expect_success 'favour same basenames even with minor differences' '
        git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
        git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
 
+test_expect_success 'two files with same basename and same content' '
+       git reset --hard &&
+       mkdir -p dir/A dir/B &&
+       cp path1 dir/A/file &&
+       cp path1 dir/B/file &&
+       git add dir &&
+       git commit -m 2 &&
+       git mv dir other-dir &&
+       git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
+'
+
 test_expect_success 'setup for many rename source candidates' '
        git reset --hard &&
        for i in 0 1 2 3 4 5 6 7 8 9;
index 04cea97f87784bfb9a9a04fdb7341478e64fd5b6..305ca7a9308685c7d040ac254fd34cebbbc86cc8 100755 (executable)
@@ -128,6 +128,18 @@ test_expect_success 'denyNonFastforwards trumps --force' '
        test "$victim_orig" = "$victim_head"
 '
 
+test_expect_success 'send-pack --all sends all branches' '
+       # make sure we have at least 2 branches with different
+       # values, just to be thorough
+       git branch other-branch HEAD^ &&
+
+       git init --bare all.git &&
+       git send-pack --all all.git &&
+       git for-each-ref refs/heads >expect &&
+       git -C all.git for-each-ref refs/heads >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'push --all excludes remote-tracking hierarchy' '
        mkdir parent &&
        (
index a3e12d295afedb170b3a917ce9d23122c1a0bc1d..44f3d5fb284e9848180df9ed9cfbc44a91762277 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 d75ef0ea2b6eb5596ee97b92529d9800411f543c..51c9669398a01bab0b59174ec6ec1be92c3f21fe 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 4008faead8ec4ea0d08e82d2916dbc3e17327c8a..4a2570ed959410ca34503c6260141f10debd752b 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 e1abd1923033617540c915af4372ba42936326d0..a41be3142e85112af7de1c2e865b7b2e261e7ebd 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 68ea31d6936cd1b19efc812cd11f63b7a6ca4fbb..e5af4b497646944d0c116f68de5b05bd6dfd7445 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 7ca10b8606372051ff1a4093e320722f7784fa6b..6ba5daf42ee870ce3a30c4fdb5a7cf84c18a2c84 100755 (executable)
@@ -177,6 +177,26 @@ test_expect_success 'test messages from "foreach --recursive"' '
        test_i18ncmp expect actual
 '
 
+cat > expect <<EOF
+Entering '../nested1'
+Entering '../nested1/nested2'
+Entering '../nested1/nested2/nested3'
+Entering '../nested1/nested2/nested3/submodule'
+Entering '../sub1'
+Entering '../sub2'
+Entering '../sub3'
+EOF
+
+test_expect_success 'test messages from "foreach --recursive" from subdirectory' '
+       (
+               cd clone2 &&
+               mkdir untracked &&
+               cd untracked &&
+               git submodule foreach --recursive >../../actual
+       ) &&
+       test_i18ncmp expect actual
+'
+
 cat > expect <<EOF
 nested1-nested1
 nested2-nested2
@@ -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 63e04277f99a08b15e12c3392f9e128147180fad..900f7de05a67424c867e0f149e7e7a448111791c 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 6568429753d5f90426ad6acc0703b79e9f7a3284..a9b266f0d3d029229a346fb21c703acb84e4c37d 100755 (executable)
@@ -212,4 +212,18 @@ test_expect_success 'blame file with CRLF attributes text' '
        grep "A U Thor" actual
 '
 
+test_expect_success 'blame file with CRLF core.autocrlf=true' '
+       git config core.autocrlf false &&
+       printf "testcase\r\n" >crlfinrepo &&
+       >.gitattributes &&
+       git add crlfinrepo &&
+       git commit -m "add crlfinrepo" &&
+       git config core.autocrlf true &&
+       mv crlfinrepo tmp &&
+       git checkout crlfinrepo &&
+       rm tmp &&
+       git blame crlfinrepo >actual &&
+       grep "A U Thor" actual
+'
+
 test_done
index 66d3fc91a739a7f04bb73694a2f5d75034045d10..eb9a8ed197b4c6383a4c688f5e5c3a3fcb102b12 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
 }