Merge branch 'nd/test-helpers'
authorJunio C Hamano <gitster@pobox.com>
Fri, 29 Apr 2016 19:59:06 +0000 (12:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 29 Apr 2016 19:59:06 +0000 (12:59 -0700)
Sources to many test helper binaries (and the generated helpers)
have been moved to t/helper/ subdirectory to reduce clutter at the
top level of the tree.

* nd/test-helpers:
test helpers: move test-* to t/helper/ subdirectory
Makefile: clean *.o files we create

45 files changed:
Documentation/RelNotes/2.9.0.txt
Makefile
branch.c
branch.h
builtin/branch.c
builtin/checkout.c
builtin/commit.c
builtin/fsck.c
builtin/merge.c
builtin/receive-pack.c
builtin/submodule--helper.c
compat/apple-common-crypto.h
config.c
config.mak.uname
configure.ac
git-compat-util.h
git-merge-octopus.sh
git-send-email.perl
git-submodule.sh
http-backend.c
http.c
ident.c
imap-send.c
po/fr.po
refs.c
refs.h
refs/files-backend.c
refs/refs-internal.h
rerere.c
rerere.h
setup.c
t/t1020-subdirectory.sh
t/t1410-reflog.sh
t/t1430-bad-ref-name.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3404-rebase-interactive.sh
t/t4200-rerere.sh
t/t5532-fetch-proxy.sh
t/t6044-merge-unrelated-index-changes.sh [new file with mode: 0755]
t/t7400-submodule-basic.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7501-commit.sh
t/t7605-merge-resolve.sh
index 7bf95f8519110271cd11df0c7f75563bdf99f7a2..36fe3c44048c759d89bc40e7ac4b78ff75a990fe 100644 (file)
@@ -80,6 +80,15 @@ UI, Workflows & Features
    such a case, and allows the users to override it with a new option,
    "--no-expand-tabs".
 
+ * "git send-email" now uses a more readable timestamps when
+   formulating a message ID.
+   (merge f916ab0 ew/send-email-readable-message-id later to maint).
+
+ * "git rerere" can encounter two or more files with the same conflict
+   signature that have to be resolved in different ways, but there was
+   no way to record these separate resolutions.
+   (merge 890fca8 jc/rerere-multi later to maint).
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -120,6 +129,9 @@ Performance, Internal Implementation, Development Support etc.
    Git repository.
    (merge 274db84 jk/check-repository-format later to maint).
 
+ * Code restructuring around the "refs" area to prepare for pluggable
+   refs backends.
+
 
 Also contains various documentation updates and code clean-ups.
 
@@ -179,6 +191,11 @@ notes for details).
    which was wrong.
    (merge f292244 ky/branch-d-worktree later to maint).
 
+ * 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.
+   (merge 18eb3a9 ky/branch-m-worktree later to maint).
+
  * "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.
@@ -201,6 +218,64 @@ notes for details).
    corrected.
    (merge a08feb8 tb/blame-force-read-cache-to-workaround-safe-crlf later to maint).
 
+ * 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).
+   (merge 95c38fb jk/branch-shortening-funny-symrefs later to maint).
+
+ * 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.
+   (merge 1f15ba1 sb/submodule-helper-clone-regression-fix later to maint).
+
+ * "git commit" misbehaved in a few minor ways when an empty message
+   is given via -m '', all of which has been corrected.
+   (merge 27014cb ad/commit-have-m-option later to maint).
+
+ * Support for CRAM-MD5 authentication method in "git imap-send" did
+   not work well.
+   (merge eb94ee7 ky/imap-send later to maint).
+
+ * Upcoming OpenSSL 1.1.0 will break compilation b updating a few APIs
+   we use in imap-send, which has been adjusted for the change.
+   (merge 1245c74 ky/imap-send-openssl-1.1.0 later to maint).
+
+ * The socks5:// proxy support added back in 2.6.4 days was not aware
+   that socks5h:// proxies behave differently.
+   (merge 87f8a0b jc/http-socks5h later to maint).
+
+ * "git config" had a codepath that tried to pass a NULL to
+   printf("%s"), which nobody seems to have noticed.
+   (merge 1cae428 jk/do-not-printf-NULL later to maint).
+
+ * On Cygwin, object creation uses the "create a temporary and then
+   rename it to the final name" pattern, not "create a temporary,
+   hardlink it to the final name and then unlink the temporary"
+   pattern.
+
+   This is necessary to use Git on Windows shared directories, and is
+   already enabled for the MinGW and plain Windows builds.  It also
+   has been used in Cygwin packaged versions of Git for quite a while.
+   See http://thread.gmane.org/gmane.comp.version-control.git/291853
+   (merge e53a64b ad/cygwin-wants-rename later to maint).
+
+ * "merge-octopus" strategy did not ensure that the index is clean
+   when merge begins.
+
+ * When "git merge" notices that the merge can be resolved purely at
+   the tree level (without having to merge blobs) and the resulting
+   tree happens to already exist in the object store, it forgot to
+   update the index, which lead to an inconsistent state for later
+   operations.
+
+ * "git submodule" reports the paths of submodules the command
+   recurses into, but this was incorrect when the command was not run
+   from the root level of the superproject.
+   (merge 2ab5660 sb/submodule-path-misc-bugs later to maint).
+
  * Other minor clean-ups and documentation updates
    (merge aed7480 mm/lockfile-error-message later to maint).
    (merge bfee614 jc/index-pack later to maint).
@@ -218,3 +293,6 @@ notes for details).
    (merge 8e9b208 js/mingw-tests-2.8 later to maint).
    (merge d55de70 jc/makefile-redirection-stderr later to maint).
    (merge 4232b21 ep/trace-doc-sample-fix later to maint).
+   (merge ef8c95e ew/send-email-drop-data-dumper later to maint).
+   (merge 24041d6 jc/xstrfmt-null-with-prec-0 later to maint).
+   (merge 7bec7f5 jk/use-write-script-more later to maint).
index 69d32bfe31e9cca783480482526ce8b1d88acde1..6c4e2dbbc81866035d7e82b72b1817032e8cdfab 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -355,9 +355,6 @@ all::
 #
 # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt.
 #
-# Define NO_HMAC_CTX_CLEANUP if your OpenSSL is version 0.9.6b or earlier to
-# cleanup the HMAC context with the older HMAC_cleanup function.
-#
 # Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
 # compiles the following initialization:
 #
@@ -1138,9 +1135,6 @@ ifndef NO_OPENSSL
        ifdef NEEDS_CRYPTO_WITH_SSL
                OPENSSL_LIBSSL += -lcrypto
        endif
-       ifdef NO_HMAC_CTX_CLEANUP
-               BASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP
-       endif
 else
        BASIC_CFLAGS += -DNO_OPENSSL
        BLK_SHA1 = 1
index 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 8885d9f8e2cdbd6b7c3e1ff753147f57ffd37243..0adba629d2ae35a4db74fb9d1450b54bb11e51e3 100644 (file)
@@ -399,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();
@@ -431,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),
@@ -442,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" */
@@ -558,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 efcbd8f6b5e0b7d551c88c02b6d7d3069a47944d..ea2fe1cf3fc251dcab2f2d70e737a0f8f5000a21 100644 (file)
@@ -242,7 +242,6 @@ static int checkout_paths(const struct checkout_opts *opts,
        struct checkout state;
        static char *ps_matched;
        unsigned char rev[20];
-       int flag;
        struct commit *head;
        int errs = 0;
        struct lock_file *lock_file;
@@ -375,7 +374,7 @@ static int checkout_paths(const struct checkout_opts *opts,
        if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
                die(_("unable to write new index file"));
 
-       read_ref_full("HEAD", 0, rev, &flag);
+       read_ref_full("HEAD", 0, rev, NULL);
        head = lookup_commit_reference_gently(rev, 1);
 
        errs |= post_checkout_hook(head, head, 0);
index 98e15276df3927e9b4af990d6514b2d511f731e7..391126e58d12f4683e131a310d94d6de4571c7e6 100644 (file)
@@ -695,7 +695,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                }
        }
 
-       if (message.len) {
+       if (have_option_m) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
@@ -1172,9 +1172,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
                f++;
        if (f > 1)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
-       if (message.len && f > 0)
+       if (have_option_m && f > 0)
                die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
-       if (f || message.len)
+       if (f || have_option_m)
                template_file = NULL;
        if (edit_message)
                use_message = edit_message;
index 55eac756f7c83e496daae7cb8874552caaa58ff5..3f27456883ede5efa2a57cfa9e2dcc889e952338 100644 (file)
@@ -493,13 +493,12 @@ static void fsck_object_dir(const char *path)
 
 static int fsck_head_link(void)
 {
-       int flag;
        int null_is_error = 0;
 
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
-       head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, &flag);
+       head_points_at = resolve_ref_unsafe("HEAD", 0, head_oid.hash, NULL);
        if (!head_points_at) {
                errors_found |= ERROR_REFS;
                return error("Invalid HEAD");
index 41467e427770d02f40df5e8d5ca0a27ab483c4af..b555a1bf9cd46ea57bcbe1c8d8b7ac146fe9f81c 100644 (file)
@@ -822,6 +822,14 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
 {
        unsigned char result_tree[20], result_commit[20];
        struct commit_list *parents, **pptr = &parents;
+       static struct lock_file lock;
+
+       hold_locked_index(&lock, 1);
+       refresh_cache(REFRESH_QUIET);
+       if (active_cache_changed &&
+           write_locked_index(&the_index, &lock, COMMIT_LOCK))
+               return error(_("Unable to write index."));
+       rollback_lock_file(&lock);
 
        write_tree_trivial(result_tree);
        printf(_("Wonderful.\n"));
@@ -1168,7 +1176,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
-       int flag, i, ret = 0, head_subsumed;
+       int i, ret = 0, head_subsumed;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
@@ -1182,7 +1190,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag);
+       branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
        if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
index 220a899b960a9ddc39ab1e582847ec87d9adad6f..a744437b5876171250d4731d181f7b30e5444187 100644 (file)
@@ -1084,13 +1084,13 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
        if (!(flag & REF_ISSYMREF))
                return;
 
-       dst_name = strip_namespace(dst_name);
        if (!dst_name) {
                rp_error("refusing update to broken symref '%s'", cmd->ref_name);
                cmd->skip_update = 1;
                cmd->error_string = "broken symref";
                return;
        }
+       dst_name = strip_namespace(dst_name);
 
        if ((item = string_list_lookup(list, dst_name)) == NULL)
                return;
index d36e8a0ec43af81a9c55d772dc99b54de7f7a58d..3bd6883eff842ee139a3d24475401d75f65fe53e 100644 (file)
@@ -196,11 +196,11 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url
 
 static int module_clone(int argc, const char **argv, const char *prefix)
 {
-       const char *path = NULL, *name = NULL, *url = NULL;
+       const char *name = NULL, *url = NULL;
        const char *reference = NULL, *depth = NULL;
        int quiet = 0;
        FILE *submodule_dot_git;
-       char *sm_gitdir, *cwd, *p;
+       char *p, *path = NULL, *sm_gitdir;
        struct strbuf rel_path = STRBUF_INIT;
        struct strbuf sb = STRBUF_INIT;
 
@@ -237,12 +237,19 @@ 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 (argc || !url || !path)
+       if (argc || !url || !path || !*path)
                usage_with_options(git_submodule_helper_usage,
                                   module_clone_options);
 
        strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
-       sm_gitdir = strbuf_detach(&sb, NULL);
+       sm_gitdir = xstrdup(absolute_path(sb.buf));
+       strbuf_reset(&sb);
+
+       if (!is_absolute_path(path)) {
+               strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
+               path = strbuf_detach(&sb, NULL);
+       } else
+               path = xstrdup(path);
 
        if (!file_exists(sm_gitdir)) {
                if (safe_create_leading_directories_const(sm_gitdir) < 0)
@@ -259,45 +266,30 @@ static int module_clone(int argc, const char **argv, const char *prefix)
        }
 
        /* Write a .git file in the submodule to redirect to the superproject. */
-       if (safe_create_leading_directories_const(path) < 0)
-               die(_("could not create directory '%s'"), path);
-
-       if (path && *path)
-               strbuf_addf(&sb, "%s/.git", path);
-       else
-               strbuf_addstr(&sb, ".git");
-
+       strbuf_addf(&sb, "%s/.git", path);
        if (safe_create_leading_directories_const(sb.buf) < 0)
                die(_("could not create leading directories of '%s'"), sb.buf);
        submodule_dot_git = fopen(sb.buf, "w");
        if (!submodule_dot_git)
                die_errno(_("cannot open file '%s'"), sb.buf);
 
-       fprintf(submodule_dot_git, "gitdir: %s\n",
-               relative_path(sm_gitdir, path, &rel_path));
+       fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
+                      relative_path(sm_gitdir, path, &rel_path));
        if (fclose(submodule_dot_git))
                die(_("could not close file %s"), sb.buf);
        strbuf_reset(&sb);
        strbuf_reset(&rel_path);
 
-       cwd = xgetcwd();
        /* Redirect the worktree of the submodule in the superproject's config */
-       if (!is_absolute_path(sm_gitdir)) {
-               strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
-               free(sm_gitdir);
-               sm_gitdir = strbuf_detach(&sb, NULL);
-       }
-
-       strbuf_addf(&sb, "%s/%s", cwd, path);
        p = git_pathdup_submodule(path, "config");
        if (!p)
                die(_("could not get submodule directory for '%s'"), path);
        git_config_set_in_file(p, "core.worktree",
-                              relative_path(sb.buf, sm_gitdir, &rel_path));
+                              relative_path(path, sm_gitdir, &rel_path));
        strbuf_release(&sb);
        strbuf_release(&rel_path);
        free(sm_gitdir);
-       free(cwd);
+       free(path);
        free(p);
        return 0;
 }
index 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 4c926990a3002884ebf3c19ab0ea7fd4ea762364..10b5c957ae714e850c9029ac23f88be407db3173 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.");
                }
@@ -2217,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,
@@ -2400,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 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 8643f74cb09f278c37851c418e839c7d160f36ca..dc2fd1b5a47b3d759aa251549d2c158713ccfa4e 100755 (executable)
@@ -44,6 +44,12 @@ esac
 # MRC is the current "merge reference commit"
 # MRT is the current "merge result tree"
 
+if ! git diff-index --quiet --cached HEAD --
+then
+    echo "Error: Your local changes to the following files would be overwritten by merge"
+    git diff-index --cached --name-only HEAD -- | sed -e 's/^/    /'
+    exit 2
+fi
 MRC=$(git rev-parse --verify -q $head)
 MRT=$(git write-tree)
 NON_FF_MERGE=0
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 cd749f473c5fc8bdd16018c5b735a215176230bb..2a84d7e66a0e6c2bbd0bee1c3e796e4580feaea9 100755 (executable)
@@ -423,8 +423,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/"
@@ -444,7 +444,7 @@ cmd_foreach()
                                        cmd_foreach "--recursive" "$@"
                                fi
                        ) <&3 3<&- ||
-                       die "$(eval_gettext "Stopping at '\$prefix\$displaypath'; script returned non-zero status.")"
+                       die "$(eval_gettext "Stopping at '\$displaypath'; script returned non-zero status.")"
                fi
        done
 }
@@ -483,7 +483,7 @@ cmd_init()
                die_if_unmatched "$mode"
                name=$(git submodule--helper name "$sm_path") || exit
 
-               displaypath=$(relative_path "$sm_path")
+               displaypath=$(relative_path "$prefix$sm_path")
 
                # Copy url setting when it is not set yet
                if test -z "$(git config "submodule.$name.url")"
@@ -800,8 +800,8 @@ cmd_update()
                                ;;
                        !*)
                                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
                                ;;
                        *)
@@ -1157,6 +1157,7 @@ cmd_status()
                        (
                                prefix="$displaypath/"
                                sanitize_submodule_env
+                               wt_prefix=
                                cd "$sm_path" &&
                                eval cmd_status
                        ) ||
index 8870a2681eb375505e4273b03a0d3e034e59a48e..214881459d828101fa0927321c5a8facb3a540f0 100644 (file)
@@ -484,9 +484,9 @@ static int show_head_ref(const char *refname, const struct object_id *oid,
                const char *target = resolve_ref_unsafe(refname,
                                                        RESOLVE_REF_READING,
                                                        unused.hash, NULL);
-               const char *target_nons = strip_namespace(target);
 
-               strbuf_addf(buf, "ref: %s\n", target_nons);
+               if (target)
+                       strbuf_addf(buf, "ref: %s\n", strip_namespace(target));
        } else {
                strbuf_addf(buf, "%s\n", oid_to_hex(oid));
        }
diff --git a/http.c b/http.c
index 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");
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.c b/refs.c
index b0e6ece6f43437bb7d59490d64802baa3b277090..87dc82f1d87d6c0969b97f94035233bec0bc3729 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1080,3 +1080,152 @@ int rename_ref_available(const char *oldname, const char *newname)
        strbuf_release(&err);
        return ret;
 }
+
+int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       struct object_id oid;
+       int flag;
+
+       if (submodule) {
+               if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
+                       return fn("HEAD", &oid, 0, cb_data);
+
+               return 0;
+       }
+
+       if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
+               return fn("HEAD", &oid, flag, cb_data);
+
+       return 0;
+}
+
+int head_ref(each_ref_fn fn, void *cb_data)
+{
+       return head_ref_submodule(NULL, fn, cb_data);
+}
+
+int for_each_ref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
+}
+
+int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
+}
+
+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
+}
+
+int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
+{
+       unsigned int flag = 0;
+
+       if (broken)
+               flag = DO_FOR_EACH_INCLUDE_BROKEN;
+       return do_for_each_ref(NULL, prefix, fn, 0, flag, cb_data);
+}
+
+int for_each_ref_in_submodule(const char *submodule, const char *prefix,
+               each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
+}
+
+int for_each_replace_ref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(NULL, git_replace_ref_base, fn,
+                              strlen(git_replace_ref_base), 0, cb_data);
+}
+
+int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret;
+       strbuf_addf(&buf, "%srefs/", get_git_namespace());
+       ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
+       strbuf_release(&buf);
+       return ret;
+}
+
+int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(NULL, "", fn, 0,
+                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
+}
+
+/* This function needs to return a meaningful errno on failure */
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+                              unsigned char *sha1, int *flags)
+{
+       static struct strbuf sb_refname = STRBUF_INIT;
+       int unused_flags;
+       int symref_count;
+
+       if (!flags)
+               flags = &unused_flags;
+
+       *flags = 0;
+
+       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+               if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+                   !refname_is_safe(refname)) {
+                       errno = EINVAL;
+                       return NULL;
+               }
+
+               /*
+                * dwim_ref() uses REF_ISBROKEN to distinguish between
+                * missing refs and refs that were present but invalid,
+                * to complain about the latter to stderr.
+                *
+                * We don't know whether the ref exists, so don't set
+                * REF_ISBROKEN yet.
+                */
+               *flags |= REF_BAD_NAME;
+       }
+
+       for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
+               unsigned int read_flags = 0;
+
+               if (read_raw_ref(refname, sha1, &sb_refname, &read_flags)) {
+                       *flags |= read_flags;
+                       if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING))
+                               return NULL;
+                       hashclr(sha1);
+                       if (*flags & REF_BAD_NAME)
+                               *flags |= REF_ISBROKEN;
+                       return refname;
+               }
+
+               *flags |= read_flags;
+
+               if (!(read_flags & REF_ISSYMREF)) {
+                       if (*flags & REF_BAD_NAME) {
+                               hashclr(sha1);
+                               *flags |= REF_ISBROKEN;
+                       }
+                       return refname;
+               }
+
+               refname = sb_refname.buf;
+               if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
+                       hashclr(sha1);
+                       return refname;
+               }
+               if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+                       if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+                           !refname_is_safe(refname)) {
+                               errno = EINVAL;
+                               return NULL;
+                       }
+
+                       *flags |= REF_ISBROKEN | REF_BAD_NAME;
+               }
+       }
+
+       errno = ELOOP;
+       return NULL;
+}
diff --git a/refs.h b/refs.h
index 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..1f38076411dc62b82792b677d95fe775effc79cd 100644 (file)
@@ -513,9 +513,6 @@ static void sort_ref_dir(struct ref_dir *dir)
        dir->sorted = dir->nr = i;
 }
 
-/* Include broken references in a do_for_each_ref*() iteration: */
-#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
-
 /*
  * Return true iff the reference described by entry can be resolved to
  * an object in the database.  Emit a warning if the referred-to
@@ -1272,8 +1269,6 @@ static struct ref_dir *get_loose_refs(struct ref_cache *refs)
        return get_ref_dir(refs->loose);
 }
 
-/* We allow "recursive" symbolic refs. Only within reason, though */
-#define MAXDEPTH 5
 #define MAXREFLEN (1024)
 
 /*
@@ -1303,7 +1298,7 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
        char buffer[128], *p;
        char *path;
 
-       if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
+       if (recursion > SYMREF_MAXDEPTH || strlen(refname) > MAXREFLEN)
                return -1;
        path = *refs->name
                ? git_pathdup_submodule(refs->name, "%s", refname)
@@ -1371,13 +1366,11 @@ static struct ref_entry *get_packed_ref(const char *refname)
 }
 
 /*
- * A loose ref file doesn't exist; check for a packed ref.  The
- * options are forwarded from resolve_safe_unsafe().
+ * A loose ref file doesn't exist; check for a packed ref.
  */
 static int resolve_missing_loose_ref(const char *refname,
-                                    int resolve_flags,
                                     unsigned char *sha1,
-                                    int *flags)
+                                    unsigned int *flags)
 {
        struct ref_entry *entry;
 
@@ -1388,205 +1381,158 @@ static int resolve_missing_loose_ref(const char *refname,
        entry = get_packed_ref(refname);
        if (entry) {
                hashcpy(sha1, entry->u.value.oid.hash);
-               if (flags)
-                       *flags |= REF_ISPACKED;
-               return 0;
-       }
-       /* The reference is not a packed reference, either. */
-       if (resolve_flags & RESOLVE_REF_READING) {
-               errno = ENOENT;
-               return -1;
-       } else {
-               hashclr(sha1);
+               *flags |= REF_ISPACKED;
                return 0;
        }
+       /* refname is not a packed reference. */
+       return -1;
 }
 
-/* This function needs to return a meaningful errno on failure */
-static const char *resolve_ref_1(const char *refname,
-                                int resolve_flags,
-                                unsigned char *sha1,
-                                int *flags,
-                                struct strbuf *sb_refname,
-                                struct strbuf *sb_path,
-                                struct strbuf *sb_contents)
+/*
+ * Read a raw ref from the filesystem or packed refs file.
+ *
+ * If the ref is a sha1, fill in sha1 and return 0.
+ *
+ * If the ref is symbolic, fill in *symref with the referrent
+ * (e.g. "refs/heads/master") and return 0.  The caller is responsible
+ * for validating the referrent.  Set REF_ISSYMREF in flags.
+ *
+ * If the ref doesn't exist, set errno to ENOENT and return -1.
+ *
+ * If the ref exists but is neither a symbolic ref nor a sha1, it is
+ * broken. Set REF_ISBROKEN in flags, set errno to EINVAL, and return
+ * -1.
+ *
+ * If there is another error reading the ref, set errno appropriately and
+ * return -1.
+ *
+ * Backend-specific flags might be set in flags as well, regardless of
+ * outcome.
+ *
+ * sb_path is workspace: the caller should allocate and free it.
+ *
+ * It is OK for refname to point into symref. In this case:
+ * - if the function succeeds with REF_ISSYMREF, symref will be
+ *   overwritten and the memory pointed to by refname might be changed
+ *   or even freed.
+ * - in all other cases, symref will be untouched, and therefore
+ *   refname will still be valid and unchanged.
+ */
+int read_raw_ref(const char *refname, unsigned char *sha1,
+                struct strbuf *symref, unsigned int *flags)
 {
-       int depth = MAXDEPTH;
-       int bad_name = 0;
+       struct strbuf sb_contents = STRBUF_INIT;
+       struct strbuf sb_path = STRBUF_INIT;
+       const char *path;
+       const char *buf;
+       struct stat st;
+       int fd;
+       int ret = -1;
+       int save_errno;
 
-       if (flags)
-               *flags = 0;
+       strbuf_reset(&sb_path);
+       strbuf_git_path(&sb_path, "%s", refname);
+       path = sb_path.buf;
 
-       if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
-               if (flags)
-                       *flags |= REF_BAD_NAME;
+stat_ref:
+       /*
+        * We might have to loop back here to avoid a race
+        * condition: first we lstat() the file, then we try
+        * to read it as a link or as a file.  But if somebody
+        * changes the type of the file (file <-> directory
+        * <-> symlink) between the lstat() and reading, then
+        * we don't want to report that as an error but rather
+        * try again starting with the lstat().
+        */
 
-               if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
-                   !refname_is_safe(refname)) {
-                       errno = EINVAL;
-                       return NULL;
+       if (lstat(path, &st) < 0) {
+               if (errno != ENOENT)
+                       goto out;
+               if (resolve_missing_loose_ref(refname, sha1, flags)) {
+                       errno = ENOENT;
+                       goto out;
                }
-               /*
-                * dwim_ref() uses REF_ISBROKEN to distinguish between
-                * missing refs and refs that were present but invalid,
-                * to complain about the latter to stderr.
-                *
-                * We don't know whether the ref exists, so don't set
-                * REF_ISBROKEN yet.
-                */
-               bad_name = 1;
+               ret = 0;
+               goto out;
        }
-       for (;;) {
-               const char *path;
-               struct stat st;
-               char *buf;
-               int fd;
-
-               if (--depth < 0) {
-                       errno = ELOOP;
-                       return NULL;
-               }
-
-               strbuf_reset(sb_path);
-               strbuf_git_path(sb_path, "%s", refname);
-               path = sb_path->buf;
 
-               /*
-                * We might have to loop back here to avoid a race
-                * condition: first we lstat() the file, then we try
-                * to read it as a link or as a file.  But if somebody
-                * changes the type of the file (file <-> directory
-                * <-> symlink) between the lstat() and reading, then
-                * we don't want to report that as an error but rather
-                * try again starting with the lstat().
-                */
-       stat_ref:
-               if (lstat(path, &st) < 0) {
-                       if (errno != ENOENT)
-                               return NULL;
-                       if (resolve_missing_loose_ref(refname, resolve_flags,
-                                                     sha1, flags))
-                               return NULL;
-                       if (bad_name) {
-                               hashclr(sha1);
-                               if (flags)
-                                       *flags |= REF_ISBROKEN;
-                       }
-                       return refname;
-               }
-
-               /* Follow "normalized" - ie "refs/.." symlinks by hand */
-               if (S_ISLNK(st.st_mode)) {
-                       strbuf_reset(sb_contents);
-                       if (strbuf_readlink(sb_contents, path, 0) < 0) {
-                               if (errno == ENOENT || errno == EINVAL)
-                                       /* inconsistent with lstat; retry */
-                                       goto stat_ref;
-                               else
-                                       return NULL;
-                       }
-                       if (starts_with(sb_contents->buf, "refs/") &&
-                           !check_refname_format(sb_contents->buf, 0)) {
-                               strbuf_swap(sb_refname, sb_contents);
-                               refname = sb_refname->buf;
-                               if (flags)
-                                       *flags |= REF_ISSYMREF;
-                               if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
-                                       hashclr(sha1);
-                                       return refname;
-                               }
-                               continue;
-                       }
-               }
-
-               /* Is it a directory? */
-               if (S_ISDIR(st.st_mode)) {
-                       errno = EISDIR;
-                       return NULL;
-               }
-
-               /*
-                * Anything else, just open it and try to use it as
-                * a ref
-                */
-               fd = open(path, O_RDONLY);
-               if (fd < 0) {
-                       if (errno == ENOENT)
+       /* Follow "normalized" - ie "refs/.." symlinks by hand */
+       if (S_ISLNK(st.st_mode)) {
+               strbuf_reset(&sb_contents);
+               if (strbuf_readlink(&sb_contents, path, 0) < 0) {
+                       if (errno == ENOENT || errno == EINVAL)
                                /* inconsistent with lstat; retry */
                                goto stat_ref;
                        else
-                               return NULL;
+                               goto out;
                }
-               strbuf_reset(sb_contents);
-               if (strbuf_read(sb_contents, fd, 256) < 0) {
-                       int save_errno = errno;
-                       close(fd);
-                       errno = save_errno;
-                       return NULL;
+               if (starts_with(sb_contents.buf, "refs/") &&
+                   !check_refname_format(sb_contents.buf, 0)) {
+                       strbuf_swap(&sb_contents, symref);
+                       *flags |= REF_ISSYMREF;
+                       ret = 0;
+                       goto out;
                }
-               close(fd);
-               strbuf_rtrim(sb_contents);
+       }
 
-               /*
-                * Is it a symbolic ref?
-                */
-               if (!starts_with(sb_contents->buf, "ref:")) {
-                       /*
-                        * Please note that FETCH_HEAD has a second
-                        * line containing other data.
-                        */
-                       if (get_sha1_hex(sb_contents->buf, sha1) ||
-                           (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) {
-                               if (flags)
-                                       *flags |= REF_ISBROKEN;
-                               errno = EINVAL;
-                               return NULL;
-                       }
-                       if (bad_name) {
-                               hashclr(sha1);
-                               if (flags)
-                                       *flags |= REF_ISBROKEN;
-                       }
-                       return refname;
-               }
-               if (flags)
-                       *flags |= REF_ISSYMREF;
-               buf = sb_contents->buf + 4;
+       /* Is it a directory? */
+       if (S_ISDIR(st.st_mode)) {
+               errno = EISDIR;
+               goto out;
+       }
+
+       /*
+        * Anything else, just open it and try to use it as
+        * a ref
+        */
+       fd = open(path, O_RDONLY);
+       if (fd < 0) {
+               if (errno == ENOENT)
+                       /* inconsistent with lstat; retry */
+                       goto stat_ref;
+               else
+                       goto out;
+       }
+       strbuf_reset(&sb_contents);
+       if (strbuf_read(&sb_contents, fd, 256) < 0) {
+               int save_errno = errno;
+               close(fd);
+               errno = save_errno;
+               goto out;
+       }
+       close(fd);
+       strbuf_rtrim(&sb_contents);
+       buf = sb_contents.buf;
+       if (starts_with(buf, "ref:")) {
+               buf += 4;
                while (isspace(*buf))
                        buf++;
-               strbuf_reset(sb_refname);
-               strbuf_addstr(sb_refname, buf);
-               refname = sb_refname->buf;
-               if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
-                       hashclr(sha1);
-                       return refname;
-               }
-               if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
-                       if (flags)
-                               *flags |= REF_ISBROKEN;
-
-                       if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
-                           !refname_is_safe(buf)) {
-                               errno = EINVAL;
-                               return NULL;
-                       }
-                       bad_name = 1;
-               }
+
+               strbuf_reset(symref);
+               strbuf_addstr(symref, buf);
+               *flags |= REF_ISSYMREF;
+               ret = 0;
+               goto out;
        }
-}
 
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
-                              unsigned char *sha1, int *flags)
-{
-       static struct strbuf sb_refname = STRBUF_INIT;
-       struct strbuf sb_contents = STRBUF_INIT;
-       struct strbuf sb_path = STRBUF_INIT;
-       const char *ret;
+       /*
+        * Please note that FETCH_HEAD has additional
+        * data after the sha.
+        */
+       if (get_sha1_hex(buf, sha1) ||
+           (buf[40] != '\0' && !isspace(buf[40]))) {
+               *flags |= REF_ISBROKEN;
+               errno = EINVAL;
+               goto out;
+       }
+
+       ret = 0;
 
-       ret = resolve_ref_1(refname, resolve_flags, sha1, flags,
-                           &sb_refname, &sb_path, &sb_contents);
+out:
+       save_errno = errno;
        strbuf_release(&sb_path);
        strbuf_release(&sb_contents);
+       errno = save_errno;
        return ret;
 }
 
@@ -1727,10 +1673,13 @@ static int do_for_each_entry(struct ref_cache *refs, const char *base,
  * value, stop the iteration and return that value; otherwise, return
  * 0.
  */
-static int do_for_each_ref(struct ref_cache *refs, const char *base,
-                          each_ref_fn fn, int trim, int flags, void *cb_data)
+int do_for_each_ref(const char *submodule, const char *base,
+                   each_ref_fn fn, int trim, int flags, void *cb_data)
 {
        struct ref_entry_cb data;
+       struct ref_cache *refs;
+
+       refs = get_ref_cache(submodule);
        data.base = base;
        data.trim = trim;
        data.flags = flags;
@@ -1745,86 +1694,6 @@ static int do_for_each_ref(struct ref_cache *refs, const char *base,
        return do_for_each_entry(refs, base, do_one_ref, &data);
 }
 
-static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-       struct object_id oid;
-       int flag;
-
-       if (submodule) {
-               if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
-                       return fn("HEAD", &oid, 0, cb_data);
-
-               return 0;
-       }
-
-       if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
-               return fn("HEAD", &oid, flag, cb_data);
-
-       return 0;
-}
-
-int head_ref(each_ref_fn fn, void *cb_data)
-{
-       return do_head_ref(NULL, fn, cb_data);
-}
-
-int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-       return do_head_ref(submodule, fn, cb_data);
-}
-
-int for_each_ref(each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data);
-}
-
-int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data);
-}
-
-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data);
-}
-
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
-{
-       unsigned int flag = 0;
-
-       if (broken)
-               flag = DO_FOR_EACH_INCLUDE_BROKEN;
-       return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data);
-}
-
-int for_each_ref_in_submodule(const char *submodule, const char *prefix,
-               each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data);
-}
-
-int for_each_replace_ref(each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
-                              strlen(git_replace_ref_base), 0, cb_data);
-}
-
-int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
-{
-       struct strbuf buf = STRBUF_INIT;
-       int ret;
-       strbuf_addf(&buf, "%srefs/", get_git_namespace());
-       ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data);
-       strbuf_release(&buf);
-       return ret;
-}
-
-int for_each_rawref(each_ref_fn fn, void *cb_data)
-{
-       return do_for_each_ref(&ref_cache, "", fn, 0,
-                              DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
-}
-
 static void unlock_ref(struct ref_lock *lock)
 {
        /* Do not free lock->lk -- atexit() still looks at them */
@@ -2894,6 +2763,42 @@ int create_symref(const char *refname, const char *target, const char *logmsg)
        return ret;
 }
 
+int set_worktree_head_symref(const char *gitdir, const char *target)
+{
+       static struct lock_file head_lock;
+       struct ref_lock *lock;
+       struct strbuf head_path = STRBUF_INIT;
+       const char *head_rel;
+       int ret;
+
+       strbuf_addf(&head_path, "%s/HEAD", absolute_path(gitdir));
+       if (hold_lock_file_for_update(&head_lock, head_path.buf,
+                                     LOCK_NO_DEREF) < 0) {
+               struct strbuf err = STRBUF_INIT;
+               unable_to_lock_message(head_path.buf, errno, &err);
+               error("%s", err.buf);
+               strbuf_release(&err);
+               strbuf_release(&head_path);
+               return -1;
+       }
+
+       /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for
+          linked trees */
+       head_rel = remove_leading_path(head_path.buf,
+                                      absolute_path(get_git_common_dir()));
+       /* to make use of create_symref_locked(), initialize ref_lock */
+       lock = xcalloc(1, sizeof(struct ref_lock));
+       lock->lk = &head_lock;
+       lock->ref_name = xstrdup(head_rel);
+       lock->orig_ref_name = xstrdup(head_rel);
+
+       ret = create_symref_locked(lock, head_rel, target, NULL);
+
+       unlock_ref(lock); /* will free lock */
+       strbuf_release(&head_path);
+       return ret;
+}
+
 int reflog_exists(const char *refname)
 {
        struct stat st;
@@ -3445,7 +3350,8 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
         * reference itself, plus we might need to update the
         * reference if --updateref was specified:
         */
-       lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err);
+       lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, REF_NODEREF,
+                                  &type, &err);
        if (!lock) {
                error("cannot lock ref '%s': %s", refname, err.buf);
                strbuf_release(&err);
index c7dded35f47e0c30131839b3378100e5a778476c..3a4f634cb4f234806ed61e7977fc57b04baddf7c 100644 (file)
@@ -197,4 +197,19 @@ const char *find_descendant_ref(const char *dirname,
 
 int rename_ref_available(const char *oldname, const char *newname);
 
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define SYMREF_MAXDEPTH 5
+
+/* Include broken references in a do_for_each_ref*() iteration: */
+#define DO_FOR_EACH_INCLUDE_BROKEN 0x01
+
+/*
+ * The common backend for the for_each_*ref* functions
+ */
+int do_for_each_ref(const char *submodule, const char *base,
+                   each_ref_fn fn, int trim, int flags, void *cb_data);
+
+int read_raw_ref(const char *refname, unsigned char *sha1,
+                struct strbuf *symref, unsigned int *flags);
+
 #endif /* REFS_REFS_INTERNAL_H */
index 587b7e2717b14748b92a5960f79947b5fba088f0..c8b9f407872f431399a4c5c321742c32cded5711 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -8,6 +8,7 @@
 #include "ll-merge.h"
 #include "attr.h"
 #include "pathspec.h"
+#include "sha1-lookup.h"
 
 #define RESOLVED 0
 #define PUNTED 1
@@ -20,6 +21,29 @@ static int rerere_enabled = -1;
 /* automatically update cleanly resolved paths to the index */
 static int rerere_autoupdate;
 
+static int rerere_dir_nr;
+static int rerere_dir_alloc;
+
+#define RR_HAS_POSTIMAGE 1
+#define RR_HAS_PREIMAGE 2
+static struct rerere_dir {
+       unsigned char sha1[20];
+       int status_alloc, status_nr;
+       unsigned char *status;
+} **rerere_dir;
+
+static void free_rerere_dirs(void)
+{
+       int i;
+       for (i = 0; i < rerere_dir_nr; i++) {
+               free(rerere_dir[i]->status);
+               free(rerere_dir[i]);
+       }
+       free(rerere_dir);
+       rerere_dir_nr = rerere_dir_alloc = 0;
+       rerere_dir = NULL;
+}
+
 static void free_rerere_id(struct string_list_item *item)
 {
        free(item->util);
@@ -27,7 +51,33 @@ static void free_rerere_id(struct string_list_item *item)
 
 static const char *rerere_id_hex(const struct rerere_id *id)
 {
-       return id->hex;
+       return sha1_to_hex(id->collection->sha1);
+}
+
+static void fit_variant(struct rerere_dir *rr_dir, int variant)
+{
+       variant++;
+       ALLOC_GROW(rr_dir->status, variant, rr_dir->status_alloc);
+       if (rr_dir->status_nr < variant) {
+               memset(rr_dir->status + rr_dir->status_nr,
+                      '\0', variant - rr_dir->status_nr);
+               rr_dir->status_nr = variant;
+       }
+}
+
+static void assign_variant(struct rerere_id *id)
+{
+       int variant;
+       struct rerere_dir *rr_dir = id->collection;
+
+       variant = id->variant;
+       if (variant < 0) {
+               for (variant = 0; variant < rr_dir->status_nr; variant++)
+                       if (!rr_dir->status[variant])
+                               break;
+       }
+       fit_variant(rr_dir, variant);
+       id->variant = variant;
 }
 
 const char *rerere_path(const struct rerere_id *id, const char *file)
@@ -35,20 +85,103 @@ const char *rerere_path(const struct rerere_id *id, const char *file)
        if (!file)
                return git_path("rr-cache/%s", rerere_id_hex(id));
 
-       return git_path("rr-cache/%s/%s", rerere_id_hex(id), file);
+       if (id->variant <= 0)
+               return git_path("rr-cache/%s/%s", rerere_id_hex(id), file);
+
+       return git_path("rr-cache/%s/%s.%d",
+                       rerere_id_hex(id), file, id->variant);
+}
+
+static int is_rr_file(const char *name, const char *filename, int *variant)
+{
+       const char *suffix;
+       char *ep;
+
+       if (!strcmp(name, filename)) {
+               *variant = 0;
+               return 1;
+       }
+       if (!skip_prefix(name, filename, &suffix) || *suffix != '.')
+               return 0;
+
+       errno = 0;
+       *variant = strtol(suffix + 1, &ep, 10);
+       if (errno || *ep)
+               return 0;
+       return 1;
+}
+
+static void scan_rerere_dir(struct rerere_dir *rr_dir)
+{
+       struct dirent *de;
+       DIR *dir = opendir(git_path("rr-cache/%s", sha1_to_hex(rr_dir->sha1)));
+
+       if (!dir)
+               return;
+       while ((de = readdir(dir)) != NULL) {
+               int variant;
+
+               if (is_rr_file(de->d_name, "postimage", &variant)) {
+                       fit_variant(rr_dir, variant);
+                       rr_dir->status[variant] |= RR_HAS_POSTIMAGE;
+               } else if (is_rr_file(de->d_name, "preimage", &variant)) {
+                       fit_variant(rr_dir, variant);
+                       rr_dir->status[variant] |= RR_HAS_PREIMAGE;
+               }
+       }
+       closedir(dir);
+}
+
+static const unsigned char *rerere_dir_sha1(size_t i, void *table)
+{
+       struct rerere_dir **rr_dir = table;
+       return rr_dir[i]->sha1;
+}
+
+static struct rerere_dir *find_rerere_dir(const char *hex)
+{
+       unsigned char sha1[20];
+       struct rerere_dir *rr_dir;
+       int pos;
+
+       if (get_sha1_hex(hex, sha1))
+               return NULL; /* BUG */
+       pos = sha1_pos(sha1, rerere_dir, rerere_dir_nr, rerere_dir_sha1);
+       if (pos < 0) {
+               rr_dir = xmalloc(sizeof(*rr_dir));
+               hashcpy(rr_dir->sha1, sha1);
+               rr_dir->status = NULL;
+               rr_dir->status_nr = 0;
+               rr_dir->status_alloc = 0;
+               pos = -1 - pos;
+
+               /* Make sure the array is big enough ... */
+               ALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc);
+               /* ... and add it in. */
+               rerere_dir_nr++;
+               memmove(rerere_dir + pos + 1, rerere_dir + pos,
+                       (rerere_dir_nr - pos - 1) * sizeof(*rerere_dir));
+               rerere_dir[pos] = rr_dir;
+               scan_rerere_dir(rr_dir);
+       }
+       return rerere_dir[pos];
 }
 
 static int has_rerere_resolution(const struct rerere_id *id)
 {
-       struct stat st;
+       const int both = RR_HAS_POSTIMAGE|RR_HAS_PREIMAGE;
+       int variant = id->variant;
 
-       return !stat(rerere_path(id, "postimage"), &st);
+       if (variant < 0)
+               return 0;
+       return ((id->collection->status[variant] & both) == both);
 }
 
 static struct rerere_id *new_rerere_id_hex(char *hex)
 {
        struct rerere_id *id = xmalloc(sizeof(*id));
-       xsnprintf(id->hex, sizeof(id->hex), "%s", hex);
+       id->collection = find_rerere_dir(hex);
+       id->variant = -1; /* not known yet */
        return id;
 }
 
@@ -75,16 +208,26 @@ static void read_rr(struct string_list *rr)
                char *path;
                unsigned char sha1[20];
                struct rerere_id *id;
+               int variant;
 
                /* There has to be the hash, tab, path and then NUL */
                if (buf.len < 42 || get_sha1_hex(buf.buf, sha1))
                        die("corrupt MERGE_RR");
 
-               if (buf.buf[40] != '\t')
+               if (buf.buf[40] != '.') {
+                       variant = 0;
+                       path = buf.buf + 40;
+               } else {
+                       errno = 0;
+                       variant = strtol(buf.buf + 41, &path, 10);
+                       if (errno)
+                               die("corrupt MERGE_RR");
+               }
+               if (*(path++) != '\t')
                        die("corrupt MERGE_RR");
                buf.buf[40] = '\0';
-               path = buf.buf + 41;
                id = new_rerere_id_hex(buf.buf);
+               id->variant = variant;
                string_list_insert(rr, path)->util = id;
        }
        strbuf_release(&buf);
@@ -105,9 +248,16 @@ static int write_rr(struct string_list *rr, int out_fd)
                id = rr->items[i].util;
                if (!id)
                        continue;
-               strbuf_addf(&buf, "%s\t%s%c",
-                           rerere_id_hex(id),
-                           rr->items[i].string, 0);
+               assert(id->variant >= 0);
+               if (0 < id->variant)
+                       strbuf_addf(&buf, "%s.%d\t%s%c",
+                                   rerere_id_hex(id), id->variant,
+                                   rr->items[i].string, 0);
+               else
+                       strbuf_addf(&buf, "%s\t%s%c",
+                                   rerere_id_hex(id),
+                                   rr->items[i].string, 0);
+
                if (write_in_full(out_fd, buf.buf, buf.len) != buf.len)
                        die("unable to write rerere record");
 
@@ -364,103 +514,6 @@ static int handle_file(const char *path, unsigned char *sha1, const char *output
        return hunk_no;
 }
 
-/*
- * Subclass of rerere_io that reads from an in-core buffer that is a
- * strbuf
- */
-struct rerere_io_mem {
-       struct rerere_io io;
-       struct strbuf input;
-};
-
-/*
- * ... and its getline() method implementation
- */
-static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
-{
-       struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
-       char *ep;
-       size_t len;
-
-       strbuf_release(sb);
-       if (!io->input.len)
-               return -1;
-       ep = memchr(io->input.buf, '\n', io->input.len);
-       if (!ep)
-               ep = io->input.buf + io->input.len;
-       else if (*ep == '\n')
-               ep++;
-       len = ep - io->input.buf;
-       strbuf_add(sb, io->input.buf, len);
-       strbuf_remove(&io->input, 0, len);
-       return 0;
-}
-
-static int handle_cache(const char *path, unsigned char *sha1, const char *output)
-{
-       mmfile_t mmfile[3] = {{NULL}};
-       mmbuffer_t result = {NULL, 0};
-       const struct cache_entry *ce;
-       int pos, len, i, hunk_no;
-       struct rerere_io_mem io;
-       int marker_size = ll_merge_marker_size(path);
-
-       /*
-        * Reproduce the conflicted merge in-core
-        */
-       len = strlen(path);
-       pos = cache_name_pos(path, len);
-       if (0 <= pos)
-               return -1;
-       pos = -pos - 1;
-
-       while (pos < active_nr) {
-               enum object_type type;
-               unsigned long size;
-
-               ce = active_cache[pos++];
-               if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
-                       break;
-               i = ce_stage(ce) - 1;
-               if (!mmfile[i].ptr) {
-                       mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
-                       mmfile[i].size = size;
-               }
-       }
-       for (i = 0; i < 3; i++)
-               if (!mmfile[i].ptr && !mmfile[i].size)
-                       mmfile[i].ptr = xstrdup("");
-
-       /*
-        * NEEDSWORK: handle conflicts from merges with
-        * merge.renormalize set, too
-        */
-       ll_merge(&result, path, &mmfile[0], NULL,
-                &mmfile[1], "ours",
-                &mmfile[2], "theirs", NULL);
-       for (i = 0; i < 3; i++)
-               free(mmfile[i].ptr);
-
-       memset(&io, 0, sizeof(io));
-       io.io.getline = rerere_mem_getline;
-       if (output)
-               io.io.output = fopen(output, "w");
-       else
-               io.io.output = NULL;
-       strbuf_init(&io.input, 0);
-       strbuf_attach(&io.input, result.ptr, result.size, result.size);
-
-       /*
-        * Grab the conflict ID and optionally write the original
-        * contents with conflict markers out.
-        */
-       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
-       strbuf_release(&io.input);
-       if (io.io.output)
-               fclose(io.io.output);
-       return hunk_no;
-}
-
 /*
  * Look at a cache entry at "i" and see if it is not conflicting,
  * conflicting and we are willing to handle, or conflicting and
@@ -568,6 +621,33 @@ int rerere_remaining(struct string_list *merge_rr)
        return 0;
 }
 
+/*
+ * Try using the given conflict resolution "ID" to see
+ * if that recorded conflict resolves cleanly what we
+ * got in the "cur".
+ */
+static int try_merge(const struct rerere_id *id, const char *path,
+                    mmfile_t *cur, mmbuffer_t *result)
+{
+       int ret;
+       mmfile_t base = {NULL, 0}, other = {NULL, 0};
+
+       if (read_mmfile(&base, rerere_path(id, "preimage")) ||
+           read_mmfile(&other, rerere_path(id, "postimage")))
+               ret = 1;
+       else
+               /*
+                * A three-way merge. Note that this honors user-customizable
+                * low-level merge driver settings.
+                */
+               ret = ll_merge(result, path, &base, NULL, cur, "", &other, "", NULL);
+
+       free(base.ptr);
+       free(other.ptr);
+
+       return ret;
+}
+
 /*
  * Find the conflict identified by "id"; the change between its
  * "preimage" (i.e. a previous contents with conflict markers) and its
@@ -582,30 +662,20 @@ static int merge(const struct rerere_id *id, const char *path)
 {
        FILE *f;
        int ret;
-       mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
+       mmfile_t cur = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
 
        /*
         * Normalize the conflicts in path and write it out to
         * "thisimage" temporary file.
         */
-       if (handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) {
-               ret = 1;
-               goto out;
-       }
-
-       if (read_mmfile(&cur, rerere_path(id, "thisimage")) ||
-           read_mmfile(&base, rerere_path(id, "preimage")) ||
-           read_mmfile(&other, rerere_path(id, "postimage"))) {
+       if ((handle_file(path, NULL, rerere_path(id, "thisimage")) < 0) ||
+           read_mmfile(&cur, rerere_path(id, "thisimage"))) {
                ret = 1;
                goto out;
        }
 
-       /*
-        * A three-way merge. Note that this honors user-customizable
-        * low-level merge driver settings.
-        */
-       ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
+       ret = try_merge(id, path, &cur, &result);
        if (ret)
                goto out;
 
@@ -631,8 +701,6 @@ static int merge(const struct rerere_id *id, const char *path)
 
 out:
        free(cur.ptr);
-       free(base.ptr);
-       free(other.ptr);
        free(result.ptr);
 
        return ret;
@@ -661,6 +729,13 @@ static void update_paths(struct string_list *update)
                rollback_lock_file(&index_lock);
 }
 
+static void remove_variant(struct rerere_id *id)
+{
+       unlink_or_warn(rerere_path(id, "postimage"));
+       unlink_or_warn(rerere_path(id, "preimage"));
+       id->collection->status[id->variant] = 0;
+}
+
 /*
  * The path indicated by rr_item may still have conflict for which we
  * have a recorded resolution, in which case replay it and optionally
@@ -672,12 +747,47 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
                               struct string_list *update)
 {
        const char *path = rr_item->string;
-       const struct rerere_id *id = rr_item->util;
+       struct rerere_id *id = rr_item->util;
+       struct rerere_dir *rr_dir = id->collection;
+       int variant;
+
+       variant = id->variant;
+
+       /* Has the user resolved it already? */
+       if (variant >= 0) {
+               if (!handle_file(path, NULL, NULL)) {
+                       copy_file(rerere_path(id, "postimage"), path, 0666);
+                       id->collection->status[variant] |= RR_HAS_POSTIMAGE;
+                       fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+                       free_rerere_id(rr_item);
+                       rr_item->util = NULL;
+                       return;
+               }
+               /*
+                * There may be other variants that can cleanly
+                * replay.  Try them and update the variant number for
+                * this one.
+                */
+       }
+
+       /* Does any existing resolution apply cleanly? */
+       for (variant = 0; variant < rr_dir->status_nr; variant++) {
+               const int both = RR_HAS_PREIMAGE | RR_HAS_POSTIMAGE;
+               struct rerere_id vid = *id;
+
+               if ((rr_dir->status[variant] & both) != both)
+                       continue;
 
-       /* Is there a recorded resolution we could attempt to apply? */
-       if (has_rerere_resolution(id)) {
-               if (merge(id, path))
-                       return; /* failed to replay */
+               vid.variant = variant;
+               if (merge(&vid, path))
+                       continue; /* failed to replay */
+
+               /*
+                * If there already is a different variant that applies
+                * cleanly, there is no point maintaining our own variant.
+                */
+               if (0 <= id->variant && id->variant != variant)
+                       remove_variant(id);
 
                if (rerere_autoupdate)
                        string_list_insert(update, path);
@@ -685,15 +795,24 @@ static void do_rerere_one_path(struct string_list_item *rr_item,
                        fprintf(stderr,
                                "Resolved '%s' using previous resolution.\n",
                                path);
-       } else if (!handle_file(path, NULL, NULL)) {
-               /* The user has resolved it. */
-               copy_file(rerere_path(id, "postimage"), path, 0666);
-               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-       } else {
+               free_rerere_id(rr_item);
+               rr_item->util = NULL;
                return;
        }
-       free_rerere_id(rr_item);
-       rr_item->util = NULL;
+
+       /* None of the existing one applies; we need a new variant */
+       assign_variant(id);
+
+       variant = id->variant;
+       handle_file(path, NULL, rerere_path(id, "preimage"));
+       if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
+               const char *path = rerere_path(id, "postimage");
+               if (unlink(path))
+                       die_errno("cannot unlink stray '%s'", path);
+               id->collection->status[variant] &= ~RR_HAS_POSTIMAGE;
+       }
+       id->collection->status[variant] |= RR_HAS_PREIMAGE;
+       fprintf(stderr, "Recorded preimage for '%s'\n", path);
 }
 
 static int do_plain_rerere(struct string_list *rr, int fd)
@@ -731,24 +850,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                id = new_rerere_id(sha1);
                string_list_insert(rr, path)->util = id;
 
-               /*
-                * If the directory does not exist, create
-                * it.  mkdir_in_gitdir() will fail with
-                * EEXIST if there already is one.
-                *
-                * NEEDSWORK: make sure "gc" does not remove
-                * preimage without removing the directory.
-                */
-               if (mkdir_in_gitdir(rerere_path(id, NULL)))
-                       continue;
-
-               /*
-                * We are the first to encounter this
-                * conflict.  Ask handle_file() to write the
-                * normalized contents to the "preimage" file.
-                */
-               handle_file(path, NULL, rerere_path(id, "preimage"));
-               fprintf(stderr, "Recorded preimage for '%s'\n", path);
+               /* Ensure that the directory exists. */
+               mkdir_in_gitdir(rerere_path(id, NULL));
        }
 
        for (i = 0; i < rr->nr; i++)
@@ -812,12 +915,111 @@ int setup_rerere(struct string_list *merge_rr, int flags)
 int rerere(int flags)
 {
        struct string_list merge_rr = STRING_LIST_INIT_DUP;
-       int fd;
+       int fd, status;
 
        fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
-       return do_plain_rerere(&merge_rr, fd);
+       status = do_plain_rerere(&merge_rr, fd);
+       free_rerere_dirs();
+       return status;
+}
+
+/*
+ * Subclass of rerere_io that reads from an in-core buffer that is a
+ * strbuf
+ */
+struct rerere_io_mem {
+       struct rerere_io io;
+       struct strbuf input;
+};
+
+/*
+ * ... and its getline() method implementation
+ */
+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
+       char *ep;
+       size_t len;
+
+       strbuf_release(sb);
+       if (!io->input.len)
+               return -1;
+       ep = memchr(io->input.buf, '\n', io->input.len);
+       if (!ep)
+               ep = io->input.buf + io->input.len;
+       else if (*ep == '\n')
+               ep++;
+       len = ep - io->input.buf;
+       strbuf_add(sb, io->input.buf, len);
+       strbuf_remove(&io->input, 0, len);
+       return 0;
+}
+
+static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+{
+       mmfile_t mmfile[3] = {{NULL}};
+       mmbuffer_t result = {NULL, 0};
+       const struct cache_entry *ce;
+       int pos, len, i, hunk_no;
+       struct rerere_io_mem io;
+       int marker_size = ll_merge_marker_size(path);
+
+       /*
+        * Reproduce the conflicted merge in-core
+        */
+       len = strlen(path);
+       pos = cache_name_pos(path, len);
+       if (0 <= pos)
+               return -1;
+       pos = -pos - 1;
+
+       while (pos < active_nr) {
+               enum object_type type;
+               unsigned long size;
+
+               ce = active_cache[pos++];
+               if (ce_namelen(ce) != len || memcmp(ce->name, path, len))
+                       break;
+               i = ce_stage(ce) - 1;
+               if (!mmfile[i].ptr) {
+                       mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+                       mmfile[i].size = size;
+               }
+       }
+       for (i = 0; i < 3; i++)
+               if (!mmfile[i].ptr && !mmfile[i].size)
+                       mmfile[i].ptr = xstrdup("");
+
+       /*
+        * NEEDSWORK: handle conflicts from merges with
+        * merge.renormalize set, too?
+        */
+       ll_merge(&result, path, &mmfile[0], NULL,
+                &mmfile[1], "ours",
+                &mmfile[2], "theirs", NULL);
+       for (i = 0; i < 3; i++)
+               free(mmfile[i].ptr);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_mem_getline;
+       if (output)
+               io.io.output = fopen(output, "w");
+       else
+               io.io.output = NULL;
+       strbuf_init(&io.input, 0);
+       strbuf_attach(&io.input, result.ptr, result.size, result.size);
+
+       /*
+        * Grab the conflict ID and optionally write the original
+        * contents with conflict markers out.
+        */
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+       strbuf_release(&io.input);
+       if (io.io.output)
+               fclose(io.io.output);
+       return hunk_no;
 }
 
 static int rerere_forget_one_path(const char *path, struct string_list *rr)
@@ -838,6 +1040,33 @@ static int rerere_forget_one_path(const char *path, struct string_list *rr)
 
        /* Nuke the recorded resolution for the conflict */
        id = new_rerere_id(sha1);
+
+       for (id->variant = 0;
+            id->variant < id->collection->status_nr;
+            id->variant++) {
+               mmfile_t cur = { NULL, 0 };
+               mmbuffer_t result = {NULL, 0};
+               int cleanly_resolved;
+
+               if (!has_rerere_resolution(id))
+                       continue;
+
+               handle_cache(path, sha1, rerere_path(id, "thisimage"));
+               if (read_mmfile(&cur, rerere_path(id, "thisimage"))) {
+                       free(cur.ptr);
+                       return error("Failed to update conflicted state in '%s'",
+                                    path);
+               }
+               cleanly_resolved = !try_merge(id, path, &cur, &result);
+               free(result.ptr);
+               free(cur.ptr);
+               if (cleanly_resolved)
+                       break;
+       }
+
+       if (id->collection->status_nr <= id->variant)
+               return error("no remembered resolution for '%s'", path);
+
        filename = rerere_path(id, "postimage");
        if (unlink(filename))
                return (errno == ENOENT
@@ -897,29 +1126,16 @@ int rerere_forget(struct pathspec *pathspec)
  * Garbage collection support
  */
 
-/*
- * Note that this is not reentrant but is used only one-at-a-time
- * so it does not matter right now.
- */
-static struct rerere_id *dirname_to_id(const char *name)
-{
-       static struct rerere_id id;
-       xsnprintf(id.hex, sizeof(id.hex), "%s", name);
-       return &id;
-}
-
-static time_t rerere_created_at(const char *dir_name)
+static time_t rerere_created_at(struct rerere_id *id)
 {
        struct stat st;
-       struct rerere_id *id = dirname_to_id(dir_name);
 
        return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
-static time_t rerere_last_used_at(const char *dir_name)
+static time_t rerere_last_used_at(struct rerere_id *id)
 {
        struct stat st;
-       struct rerere_id *id = dirname_to_id(dir_name);
 
        return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
@@ -929,15 +1145,28 @@ static time_t rerere_last_used_at(const char *dir_name)
  */
 static void unlink_rr_item(struct rerere_id *id)
 {
-       unlink(rerere_path(id, "thisimage"));
-       unlink(rerere_path(id, "preimage"));
-       unlink(rerere_path(id, "postimage"));
-       /*
-        * NEEDSWORK: what if this rmdir() fails?  Wouldn't we then
-        * assume that we already have preimage recorded in
-        * do_plain_rerere()?
-        */
-       rmdir(rerere_path(id, NULL));
+       unlink_or_warn(rerere_path(id, "thisimage"));
+       remove_variant(id);
+       id->collection->status[id->variant] = 0;
+}
+
+static void prune_one(struct rerere_id *id, time_t now,
+                     int cutoff_resolve, int cutoff_noresolve)
+{
+       time_t then;
+       int cutoff;
+
+       then = rerere_last_used_at(id);
+       if (then)
+               cutoff = cutoff_resolve;
+       else {
+               then = rerere_created_at(id);
+               if (!then)
+                       return;
+               cutoff = cutoff_noresolve;
+       }
+       if (then < now - cutoff * 86400)
+               unlink_rr_item(id);
 }
 
 void rerere_gc(struct string_list *rr)
@@ -945,8 +1174,8 @@ void rerere_gc(struct string_list *rr)
        struct string_list to_remove = STRING_LIST_INIT_DUP;
        DIR *dir;
        struct dirent *e;
-       int i, cutoff;
-       time_t now = time(NULL), then;
+       int i;
+       time_t now = time(NULL);
        int cutoff_noresolve = 15;
        int cutoff_resolve = 60;
 
@@ -961,25 +1190,32 @@ void rerere_gc(struct string_list *rr)
                die_errno("unable to open rr-cache directory");
        /* Collect stale conflict IDs ... */
        while ((e = readdir(dir))) {
+               struct rerere_dir *rr_dir;
+               struct rerere_id id;
+               int now_empty;
+
                if (is_dot_or_dotdot(e->d_name))
                        continue;
-
-               then = rerere_last_used_at(e->d_name);
-               if (then) {
-                       cutoff = cutoff_resolve;
-               } else {
-                       then = rerere_created_at(e->d_name);
-                       if (!then)
-                               continue;
-                       cutoff = cutoff_noresolve;
+               rr_dir = find_rerere_dir(e->d_name);
+               if (!rr_dir)
+                       continue; /* or should we remove e->d_name? */
+
+               now_empty = 1;
+               for (id.variant = 0, id.collection = rr_dir;
+                    id.variant < id.collection->status_nr;
+                    id.variant++) {
+                       prune_one(&id, now, cutoff_resolve, cutoff_noresolve);
+                       if (id.collection->status[id.variant])
+                               now_empty = 0;
                }
-               if (then < now - cutoff * 86400)
+               if (now_empty)
                        string_list_append(&to_remove, e->d_name);
        }
        closedir(dir);
-       /* ... and then remove them one-by-one */
+
+       /* ... and then remove the empty directories */
        for (i = 0; i < to_remove.nr; i++)
-               unlink_rr_item(dirname_to_id(to_remove.items[i].string));
+               rmdir(git_path("rr-cache/%s", to_remove.items[i].string));
        string_list_clear(&to_remove, 0);
        rollback_lock_file(&write_lock);
 }
@@ -1000,8 +1236,10 @@ void rerere_clear(struct string_list *merge_rr)
 
        for (i = 0; i < merge_rr->nr; i++) {
                struct rerere_id *id = merge_rr->items[i].util;
-               if (!has_rerere_resolution(id))
+               if (!has_rerere_resolution(id)) {
                        unlink_rr_item(id);
+                       rmdir(rerere_path(id, NULL));
+               }
        }
        unlink_or_warn(git_path_merge_rr());
        rollback_lock_file(&write_lock);
index 1222e91921882b41ac9c1648960eca9305b27d8d..c2961feaaa8a8297d59fb13b120c1a57c8a2868a 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -16,8 +16,10 @@ struct pathspec;
  */
 extern void *RERERE_RESOLVED;
 
+struct rerere_dir;
 struct rerere_id {
-       char hex[41];
+       struct rerere_dir *collection;
+       int variant;
 };
 
 extern int setup_rerere(struct string_list *, int);
diff --git a/setup.c b/setup.c
index 1563cd42df2865d9f4390c9e68cdc666b45fcf96..c86bf5c9fabeab4b60b0696b9ae9b2f0f2bb720e 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -102,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)) {
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 c623824b4d0c2bfde08a69c242e4c5340b97abd3..9cf91dc6d217f8d92dea0125e37b03f6a6938124 100755 (executable)
@@ -338,4 +338,14 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
        test_line_count = 3 actual
 '
 
+test_expect_success 'reflog expire operates on symref not referrent' '
+       git branch -l the_symref &&
+       git branch -l referrent &&
+       git update-ref referrent HEAD &&
+       git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
+       test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
+       touch .git/refs/heads/referrent.lock &&
+       git reflog expire --expire=all the_symref
+'
+
 test_done
index c465abe8e34936db135c136a6831975b2763e623..25ddab4e984877cb9361919a893fce391d2f066a 100755 (executable)
@@ -42,7 +42,7 @@ test_expect_success 'git branch shows badly named ref as warning' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch >output 2>error &&
-       grep -e "broken\.\.\.ref" error &&
+       test_i18ngrep -e "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
@@ -147,35 +147,145 @@ test_expect_success 'rev-parse skips symref pointing to broken name' '
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
        git branch shadow one &&
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       git symbolic-ref refs/tags/shadow refs/heads/broken...ref &&
-
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/tags/shadow &&
+       test_when_finished "rm -f .git/refs/tags/shadow" &&
        git rev-parse --verify one >expect &&
        git rev-parse --verify shadow >actual 2>err &&
        test_cmp expect actual &&
-       test_i18ngrep "ignoring.*refs/tags/shadow" err
+       test_i18ngrep "ignoring dangling symref refs/tags/shadow" err
 '
 
-test_expect_success 'update-ref --no-deref -d can delete reference to broken name' '
-       git symbolic-ref refs/heads/badname refs/heads/broken...ref &&
+test_expect_success 'for-each-ref emits warnings for broken names' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
        test_when_finished "rm -f .git/refs/heads/badname" &&
-       test_path_is_file .git/refs/heads/badname &&
-       git update-ref --no-deref -d refs/heads/badname &&
-       test_path_is_missing .git/refs/heads/badname
+       printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
+       test_when_finished "rm -f .git/refs/heads/broken...symref" &&
+       git for-each-ref >output 2>error &&
+       ! grep -e "broken\.\.\.ref" output &&
+       ! grep -e "badname" output &&
+       ! grep -e "broken\.\.\.symref" output &&
+       test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error &&
+       test_i18ngrep "ignoring broken ref refs/heads/badname" error &&
+       test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error
 '
 
 test_expect_success 'update-ref -d can delete broken name' '
        cp .git/refs/heads/master .git/refs/heads/broken...ref &&
        test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git update-ref -d refs/heads/broken...ref &&
+       git update-ref -d refs/heads/broken...ref >output 2>error &&
+       test_must_be_empty output &&
+       test_must_be_empty error &&
+       git branch >output 2>error &&
+       ! grep -e "broken\.\.\.ref" error &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'branch -d can delete broken name' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch -d broken...ref >output 2>error &&
+       test_i18ngrep "Deleted branch broken...ref (was broken)" output &&
+       test_must_be_empty error &&
        git branch >output 2>error &&
        ! grep -e "broken\.\.\.ref" error &&
        ! grep -e "broken\.\.\.ref" output
 '
 
+test_expect_success 'update-ref --no-deref -d can delete symref to broken name' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       git update-ref --no-deref -d refs/heads/badname >output 2>error &&
+       test_path_is_missing .git/refs/heads/badname &&
+       test_must_be_empty output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'branch -d can delete symref to broken name' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       git branch -d badname >output 2>error &&
+       test_path_is_missing .git/refs/heads/badname &&
+       test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' '
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       git update-ref --no-deref -d refs/heads/badname >output 2>error &&
+       test_path_is_missing .git/refs/heads/badname &&
+       test_must_be_empty output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'branch -d can delete dangling symref to broken name' '
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       git branch -d badname >output 2>error &&
+       test_path_is_missing .git/refs/heads/badname &&
+       test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'update-ref -d can delete broken name through symref' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       git update-ref -d refs/heads/badname >output 2>error &&
+       test_path_is_missing .git/refs/heads/broken...ref &&
+       test_must_be_empty output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'update-ref --no-deref -d can delete symref with broken name' '
+       printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
+       test_when_finished "rm -f .git/refs/heads/broken...symref" &&
+       git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
+       test_path_is_missing .git/refs/heads/broken...symref &&
+       test_must_be_empty output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'branch -d can delete symref with broken name' '
+       printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref &&
+       test_when_finished "rm -f .git/refs/heads/broken...symref" &&
+       git branch -d broken...symref >output 2>error &&
+       test_path_is_missing .git/refs/heads/broken...symref &&
+       test_i18ngrep "Deleted branch broken...symref (was refs/heads/master)" output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' '
+       printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
+       test_when_finished "rm -f .git/refs/heads/broken...symref" &&
+       git update-ref --no-deref -d refs/heads/broken...symref >output 2>error &&
+       test_path_is_missing .git/refs/heads/broken...symref &&
+       test_must_be_empty output &&
+       test_must_be_empty error
+'
+
+test_expect_success 'branch -d can delete dangling symref with broken name' '
+       printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref &&
+       test_when_finished "rm -f .git/refs/heads/broken...symref" &&
+       git branch -d broken...symref >output 2>error &&
+       test_path_is_missing .git/refs/heads/broken...symref &&
+       test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output &&
+       test_must_be_empty error
+'
+
 test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '
        echo precious >.git/my-private-file &&
        echo precious >expect &&
-       test_must_fail git update-ref -d my-private-file &&
+       test_must_fail git update-ref -d my-private-file >output 2>error &&
+       test_must_be_empty output &&
+       test_i18ngrep -e "cannot lock .*: unable to resolve reference" error &&
        test_cmp expect .git/my-private-file
 '
 
index 508007fd377260fd46e385d6a47b8a9732ef1dd0..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' '
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 b79f442acf901db1a9049e46e93f09d44a64e975..d96d0e4c9b517ecb712d643cdd13e08bff066036 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 ed9c91e25b584c2241c238f9c6ffad9a3a327d2c..1a080e782371e2d7ad489c706a54fce1553b9579 100755 (executable)
@@ -184,12 +184,27 @@ test_expect_success 'rerere updates postimage timestamp' '
 '
 
 test_expect_success 'rerere clear' '
-       rm $rr/postimage &&
+       mv $rr/postimage .git/post-saved &&
        echo "$sha1     a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR &&
        git rerere clear &&
        ! test -d $rr
 '
 
+test_expect_success 'leftover directory' '
+       git reset --hard &&
+       mkdir -p $rr &&
+       test_must_fail git merge first &&
+       test -f $rr/preimage
+'
+
+test_expect_success 'missing preimage' '
+       git reset --hard &&
+       mkdir -p $rr &&
+       cp .git/post-saved $rr/postimage &&
+       test_must_fail git merge first &&
+       test -f $rr/preimage
+'
+
 test_expect_success 'set up for garbage collection tests' '
        mkdir -p $rr &&
        echo Hello >$rr/preimage &&
@@ -391,4 +406,157 @@ test_expect_success 'rerere -h' '
        test_i18ngrep [Uu]sage help
 '
 
+concat_insert () {
+       last=$1
+       shift
+       cat early && printf "%s\n" "$@" && cat late "$last"
+}
+
+count_pre_post () {
+       find .git/rr-cache/ -type f -name "preimage*" >actual &&
+       test_line_count = "$1" actual &&
+       find .git/rr-cache/ -type f -name "postimage*" >actual &&
+       test_line_count = "$2" actual
+}
+
+test_expect_success 'rerere gc' '
+       find .git/rr-cache -type f >original &&
+       xargs test-chmtime -172800 <original &&
+
+       git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
+       find .git/rr-cache -type f >actual &&
+       test_cmp original actual &&
+
+       git -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&
+       find .git/rr-cache -type f >actual &&
+       test_cmp original actual &&
+
+       git -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&
+       find .git/rr-cache -type f >actual &&
+       >expect &&
+       test_cmp expect actual
+'
+
+merge_conflict_resolve () {
+       git reset --hard &&
+       test_must_fail git merge six.1 &&
+       # Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)
+       concat_insert short 6.1 6.2 >file1 &&
+       concat_insert long 6.1 6.2 >file2
+}
+
+test_expect_success 'multiple identical conflicts' '
+       git reset --hard &&
+
+       test_seq 1 6 >early &&
+       >late &&
+       test_seq 11 15 >short &&
+       test_seq 111 120 >long &&
+       concat_insert short >file1 &&
+       concat_insert long >file2 &&
+       git add file1 file2 &&
+       git commit -m base &&
+       git tag base &&
+       git checkout -b six.1 &&
+       concat_insert short 6.1 >file1 &&
+       concat_insert long 6.1 >file2 &&
+       git add file1 file2 &&
+       git commit -m 6.1 &&
+       git checkout -b six.2 HEAD^ &&
+       concat_insert short 6.2 >file1 &&
+       concat_insert long 6.2 >file2 &&
+       git add file1 file2 &&
+       git commit -m 6.2 &&
+
+       # At this point, six.1 and six.2
+       # - derive from common ancestor that has two files
+       #   1...6 7 11..15 (file1) and 1...6 7 111..120 (file2)
+       # - six.1 replaces these 7s with 6.1
+       # - six.2 replaces these 7s with 6.2
+
+       merge_conflict_resolve &&
+
+       # Check that rerere knows that file1 and file2 have conflicts
+
+       printf "%s\n" file1 file2 >expect &&
+       git ls-files -u | sed -e "s/^.* //" | sort -u >actual &&
+       test_cmp expect actual &&
+
+       git rerere status | sort >actual &&
+       test_cmp expect actual &&
+
+       git rerere remaining >actual &&
+       test_cmp expect actual &&
+
+       count_pre_post 2 0 &&
+
+       # Pretend that the conflicts were made quite some time ago
+       find .git/rr-cache/ -type f | xargs test-chmtime -172800 &&
+
+       # Unresolved entries have not expired yet
+       git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
+       count_pre_post 2 0 &&
+
+       # Unresolved entries have expired
+       git -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc &&
+       count_pre_post 0 0 &&
+
+       # Recreate the conflicted state
+       merge_conflict_resolve &&
+       count_pre_post 2 0 &&
+
+       # Clear it
+       git rerere clear &&
+       count_pre_post 0 0 &&
+
+       # Recreate the conflicted state
+       merge_conflict_resolve &&
+       count_pre_post 2 0 &&
+
+       # We resolved file1 and file2
+       git rerere &&
+       >expect &&
+       git rerere remaining >actual &&
+       test_cmp expect actual &&
+
+       # We must have recorded both of them
+       count_pre_post 2 2 &&
+
+       # Now we should be able to resolve them both
+       git reset --hard &&
+       test_must_fail git merge six.1 &&
+       git rerere &&
+
+       >expect &&
+       git rerere remaining >actual &&
+       test_cmp expect actual &&
+
+       concat_insert short 6.1 6.2 >file1.expect &&
+       concat_insert long 6.1 6.2 >file2.expect &&
+       test_cmp file1.expect file1 &&
+       test_cmp file2.expect file2 &&
+
+       # Forget resolution for file2
+       git rerere forget file2 &&
+       echo file2 >expect &&
+       git rerere status >actual &&
+       test_cmp expect actual &&
+       count_pre_post 2 1 &&
+
+       # file2 already has correct resolution, so record it again
+       git rerere &&
+
+       # Pretend that the resolutions are old again
+       find .git/rr-cache/ -type f | xargs test-chmtime -172800 &&
+
+       # Resolved entries have not expired yet
+       git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
+
+       count_pre_post 2 2 &&
+
+       # Resolved entries have expired
+       git -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc &&
+       count_pre_post 0 0
+'
+
 test_done
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
diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh
new file mode 100755 (executable)
index 0000000..20a3ffe
--- /dev/null
@@ -0,0 +1,153 @@
+#!/bin/sh
+
+test_description="merges with unrelated index changes"
+
+. ./test-lib.sh
+
+# Testcase for some simple merges
+#   A
+#   o-----o B
+#    \
+#     \---o C
+#      \
+#       \-o D
+#        \
+#         o E
+#   Commit A: some file a
+#   Commit B: adds file b, modifies end of a
+#   Commit C: adds file c
+#   Commit D: adds file d, modifies beginning of a
+#   Commit E: renames a->subdir/a, adds subdir/e
+
+test_expect_success 'setup trivial merges' '
+       seq 1 10 >a &&
+       git add a &&
+       test_tick && git commit -m A &&
+
+       git branch A &&
+       git branch B &&
+       git branch C &&
+       git branch D &&
+       git branch E &&
+
+       git checkout B &&
+       echo b >b &&
+       echo 11 >>a &&
+       git add a b &&
+       test_tick && git commit -m B &&
+
+       git checkout C &&
+       echo c >c &&
+       git add c &&
+       test_tick && git commit -m C &&
+
+       git checkout D &&
+       seq 2 10 >a &&
+       echo d >d &&
+       git add a d &&
+       test_tick && git commit -m D &&
+
+       git checkout E &&
+       mkdir subdir &&
+       git mv a subdir/a &&
+       echo e >subdir/e &&
+       git add subdir &&
+       test_tick && git commit -m E
+'
+
+test_expect_success 'ff update' '
+       git reset --hard &&
+       git checkout A^0 &&
+
+       touch random_file && git add random_file &&
+
+       git merge E^0 &&
+
+       test_must_fail git rev-parse HEAD:random_file &&
+       test "$(git diff --name-only --cached E)" = "random_file"
+'
+
+test_expect_success 'ff update, important file modified' '
+       git reset --hard &&
+       git checkout A^0 &&
+
+       mkdir subdir &&
+       touch subdir/e &&
+       git add subdir/e &&
+
+       test_must_fail git merge E^0
+'
+
+test_expect_success 'resolve, trivial' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge -s resolve C^0
+'
+
+test_expect_success 'resolve, non-trivial' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge -s resolve D^0
+'
+
+test_expect_success 'recursive' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge -s recursive C^0
+'
+
+test_expect_success 'octopus, unrelated file touched' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge C^0 D^0
+'
+
+test_expect_success 'octopus, related file removed' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       git rm b &&
+
+       test_must_fail git merge C^0 D^0
+'
+
+test_expect_success 'octopus, related file modified' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       echo 12 >>a && git add a &&
+
+       test_must_fail git merge C^0 D^0
+'
+
+test_expect_success 'ours' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge -s ours C^0
+'
+
+test_expect_success 'subtree' '
+       git reset --hard &&
+       git checkout B^0 &&
+
+       touch random_file && git add random_file &&
+
+       test_must_fail git merge -s subtree E^0
+'
+
+test_done
index 17d7a9820725093b21a39acf16397399271bd961..f99f674ac795b7b55c5fc678257bd7365c71e62d 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 0791df75aca9e318077b95f2892c3a728eefc2df..fd741f506f4192e56348fcced49b2deabe59b9e7 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 0cb9d11f2171de19b260391017310f0ee792f89b..5d56c3854647b2f2bc2fdf4221b1f76f5d0b0629 100755 (executable)
@@ -27,7 +27,7 @@ test_expect_success 'setup' '
        git tag c3
 '
 
-test_expect_success 'merge c1 to c2' '
+merge_c1_to_c2_cmds='
        git reset --hard c1 &&
        git merge -s resolve c2 &&
        test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" &&
@@ -41,6 +41,10 @@ test_expect_success 'merge c1 to c2' '
        test 3 = $(git ls-files | wc -l)
 '
 
+test_expect_success 'merge c1 to c2'        "$merge_c1_to_c2_cmds"
+
+test_expect_success 'merge c1 to c2, again' "$merge_c1_to_c2_cmds"
+
 test_expect_success 'merge c2 to c3 (fails)' '
        git reset --hard c2 &&
        test_must_fail git merge -s resolve c3