]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'rs/refspec-leakfix'
authorJunio C Hamano <gitster@pobox.com>
Sat, 19 Sep 2020 00:58:00 +0000 (17:58 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 19 Sep 2020 00:58:00 +0000 (17:58 -0700)
Leakfix.

* rs/refspec-leakfix:
  refspec: add and use refspec_appendf()
  push: release strbufs used for refspec formatting

55 files changed:
.github/workflows/main.yml
Documentation/RelNotes/2.29.0.txt
Documentation/git-for-each-ref.txt
Documentation/git-imap-send.txt
Documentation/git-worktree.txt
archive.c
branch.c
builtin/checkout.c
builtin/fast-export.c
builtin/fast-import.c
builtin/init-db.c
builtin/log.c
builtin/merge.c
builtin/repack.c
builtin/reset.c
builtin/rev-parse.c
builtin/show-branch.c
builtin/stash.c
builtin/submodule--helper.c
builtin/worktree.c
bundle.c
cache.h
commit.c
compat/vcbuild/README
compat/vcbuild/scripts/clink.pl
contrib/buildsystems/Generators/Vcxproj.pm
contrib/buildsystems/engine.pl
diff-lib.c
diff.c
git-submodule.sh
midx.c
pack-bitmap-write.c
packfile.c
packfile.h
pretty.c
pretty.h
ref-filter.c
refs.c
refs.h
remote.c
revision.c
revision.h
sha1-name.c
submodule.c
submodule.h
t/t0001-init.sh
t/t2406-worktree-repair.sh [new file with mode: 0755]
t/t5319-multi-pack-index.sh
t/t6300-for-each-ref.sh
t/t7401-submodule-summary.sh
t/t7421-submodule-summary-add.sh [new file with mode: 0755]
t/t7508-status.sh
worktree.c
worktree.h
wt-status.c

index 30425404eb3fb8bc66ebf2c16803763f5d1247d0..fcfd138ff1833bcad3c34113f6de10809739e5d2 100644 (file)
@@ -12,7 +12,6 @@ jobs:
       enabled: ${{ steps.check-ref.outputs.enabled }}
     steps:
       - name: try to clone ci-config branch
-        continue-on-error: true
         run: |
           git -c protocol.version=2 clone \
             --no-tags \
@@ -23,8 +22,8 @@ jobs:
             --filter=blob:none \
             https://github.com/${{ github.repository }} \
             config-repo &&
-            cd config-repo &&
-            git checkout HEAD -- ci/config
+          cd config-repo &&
+          git checkout HEAD -- ci/config || : ignore
       - id: check-ref
         name: check whether CI is enabled for ref
         run: |
index a640fbc02f2d40cfa91fb4141d2735548fcd42f4..4adb9ce124a5d68e231e8c9de7d393b5534f207d 100644 (file)
@@ -55,6 +55,15 @@ UI, Workflows & Features
    object names of blobs involved in the patch, but its length was not
    affected by the --abbrev option.  Now it is.
 
+ * "git worktree" gained a "repair" subcommand to help users recover
+   after moving the worktrees or repository manually without telling
+   Git.  Also, "git init --separate-git-dir" no longer corrupts
+   administrative data related to linked worktrees.
+
+ * The "--format=" option to the "for-each-ref" command and friends
+   learned a few more tricks, e.g. the ":short" suffix that applies to
+   "objectname" now also can be used for "parent", "tree", etc.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -128,6 +137,14 @@ Performance, Internal Implementation, Development Support etc.
    execute the git subcommands, especially built-ins, in "git-foo"
    form, which have been corrected.
 
+ * When a packfile is removed by "git repack", multi-pack-index gets
+   cleared; the code was taught to do so less aggressively by first
+   checking if the midx actually refers to a pack that no longer
+   exists.
+
+ * Internal API clean-up to handle two options "diff-index" and "log"
+   have, which happen to share the same short form, more sensibly.
+
 
 Fixes since v2.28
 -----------------
@@ -286,6 +303,16 @@ Fixes since v2.28
    been freed, which has been fixed.
    (merge 6479ea4a8a jk/xrealloc-avoid-use-after-free later to maint).
 
+ * "git status" has trouble showing where it came from by interpreting
+   reflog entries that recordcertain events, e.g. "checkout @{u}", and
+   gives a hard/fatal error.  Even though it inherently is impossible
+   to give a correct answer because the reflog entries lose some
+   information (e.g. "@{u}" does not record what branch the user was
+   on hence which branch 'the upstream' needs to be computed, and even
+   if the record were available, the relationship between branches may
+   have changed), at least hide the error to allow "status" show its
+   output.
+
  * Other code cleanup, docfix, build fix, etc.
    (merge 84544f2ea3 sk/typofixes later to maint).
    (merge b17f411ab5 ar/help-guides-doc later to maint).
@@ -311,3 +338,4 @@ Fixes since v2.28
    (merge ee22a29215 so/pretty-abbrev-doc later to maint).
    (merge 3100fd5588 jc/post-checkout-doc later to maint).
    (merge 17bae89476 pb/doc-external-diff-env later to maint).
+   (merge 27ed6ccc12 jk/worktree-check-clean-leakfix later to maint).
index 2ea71c5f6c2c14ff6c0bda87f63f2b14b895d00b..616ce460872f0f93ff5944a36bb0b0a20920485d 100644 (file)
@@ -222,6 +222,8 @@ worktreepath::
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
+Fields `tree` and `parent` can also be used with modifier `:short` and
+`:short=<length>` just like `objectname`.
 
 For commit and tag objects, the special `creatordate` and `creator`
 fields will correspond to the appropriate date or name-email-date tuple
@@ -230,7 +232,10 @@ These are intended for working on a mix of annotated and lightweight tags.
 
 Fields that have name-email-date tuple as its value (`author`,
 `committer`, and `tagger`) can be suffixed with `name`, `email`,
-and `date` to extract the named component.
+and `date` to extract the named component.  For email fields (`authoremail`,
+`committeremail` and `taggeremail`), `:trim` can be appended to get the email
+without angle brackets, and `:localpart` to get the part before the `@` symbol
+out of the trimmed email.
 
 The message in a commit or a tag object is `contents`, from which
 `contents:<part>` can be used to extract various parts out of:
@@ -242,6 +247,9 @@ contents:subject::
        The first paragraph of the message, which typically is a
        single line, is taken as the "subject" of the commit or the
        tag message.
+       Instead of `contents:subject`, field `subject` can also be used to
+       obtain same results. `:sanitize` can be appended to `subject` for
+       subject line suitable for filename.
 
 contents:body::
        The remainder of the commit or the tag message that follows
index 65b53fcc47d6b93cd2a63309401c227cf899794f..63cf498ce9f276878ea8a7bd79362a423210d030 100644 (file)
@@ -51,17 +51,13 @@ OPTIONS
 CONFIGURATION
 -------------
 
-To use the tool, imap.folder and either imap.tunnel or imap.host must be set
+To use the tool, `imap.folder` and either `imap.tunnel` or `imap.host` must be set
 to appropriate values.
 
-Variables
-~~~~~~~~~
-
 include::config/imap.txt[]
 
-Examples
-~~~~~~~~
-
+EXAMPLES
+--------
 Using tunnel mode:
 
 ..........................
@@ -89,14 +85,18 @@ Using direct mode with SSL:
     user = bob
     pass = p4ssw0rd
     port = 123
-    sslverify = false
+    ; sslVerify = false
 .........................
 
 
-EXAMPLES
---------
-To submit patches using GMail's IMAP interface, first, edit your ~/.gitconfig
-to specify your account settings:
+[NOTE]
+You may want to use `sslVerify=false`
+while troubleshooting, if you suspect that the reason you are
+having trouble connecting is because the certificate you use at
+the private server `example.com` you are trying to set up (or
+have set up) may not be verified correctly.
+
+Using Gmail's IMAP interface:
 
 ---------
 [imap]
@@ -104,17 +104,21 @@ to specify your account settings:
        host = imaps://imap.gmail.com
        user = user@gmail.com
        port = 993
-       sslverify = false
 ---------
 
-You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+[NOTE]
+You might need to instead use: `folder = "[Google Mail]/Drafts"` if you get an error
 that the "Folder doesn't exist".
 
+[NOTE]
+If your Gmail account is set to another language than English, the name of the "Drafts"
+folder will be localized.
+
 Once the commits are ready to be sent, run the following command:
 
   $ git format-patch --cover-letter -M --stdout origin/master | git imap-send
 
-Just make sure to disable line wrapping in the email client (GMail's web
+Just make sure to disable line wrapping in the email client (Gmail's web
 interface will wrap lines no matter what, so you need to use a real
 IMAP client).
 
index 6ee6ec79824a6a8e43d2411b9226c72a49d52c9e..f70cda4b36428ac1bfe2f5fc742f8353c19097d6 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git worktree move' <worktree> <new-path>
 'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree remove' [-f] <worktree>
+'git worktree repair' [<path>...]
 'git worktree unlock' <worktree>
 
 DESCRIPTION
@@ -97,7 +98,10 @@ with `--reason`.
 move::
 
 Move a working tree to a new location. Note that the main working tree
-or linked working trees containing submodules cannot be moved.
+or linked working trees containing submodules cannot be moved with this
+command. (The `git worktree repair` command, however, can reestablish
+the connection with linked working trees if you move the main working
+tree manually.)
 
 prune::
 
@@ -110,6 +114,23 @@ and no modification in tracked files) can be removed. Unclean working
 trees or ones with submodules can be removed with `--force`. The main
 working tree cannot be removed.
 
+repair [<path>...]::
+
+Repair working tree administrative files, if possible, if they have
+become corrupted or outdated due to external factors.
++
+For instance, if the main working tree (or bare repository) is moved,
+linked working trees will be unable to locate it. Running `repair` in
+the main working tree will reestablish the connection from linked
+working trees back to the main working tree.
++
+Similarly, if a linked working tree is moved without using `git worktree
+move`, the main working tree (or bare repository) will be unable to
+locate it. Running `repair` within the recently-moved working tree will
+reestablish the connection. If multiple linked working trees are moved,
+running `repair` from any working tree with each tree's new `<path>` as
+an argument, will reestablish the connection to all the specified paths.
+
 unlock::
 
 Unlock a working tree, allowing it to be pruned, moved or deleted.
@@ -303,7 +324,8 @@ in the entry's directory. For example, if a linked working tree is moved
 to `/newpath/test-next` and its `.git` file points to
 `/path/main/.git/worktrees/test-next`, then update
 `/path/main/.git/worktrees/test-next/gitdir` to reference `/newpath/test-next`
-instead.
+instead. Better yet, run `git worktree repair` to reestablish the connection
+automatically.
 
 To prevent a `$GIT_DIR/worktrees` entry from being pruned (which
 can be useful in some situations, such as when the
index fb39706120cdd6b668fbc10cefad608692587d09..0de6048bfccb8df89a1fce9abbeb3d0f65f004fc 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -397,10 +397,10 @@ static void parse_treeish_arg(const char **argv,
                const char *colon = strchrnul(name, ':');
                int refnamelen = colon - name;
 
-               if (!dwim_ref(name, refnamelen, &oid, &ref))
+               if (!dwim_ref(name, refnamelen, &oid, &ref, 0))
                        die(_("no such ref: %.*s"), refnamelen, name);
        } else {
-               dwim_ref(name, strlen(name), &oid, &ref);
+               dwim_ref(name, strlen(name), &oid, &ref, 0);
        }
 
        if (get_oid(name, &oid))
index 7095f7805869811c07aa25d9fbfd9c68ddc7d08d..9c9dae1eae321c6878607a3b624a187fcb176380 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -281,7 +281,7 @@ void create_branch(struct repository *r,
                die(_("Not a valid object name: '%s'."), start_name);
        }
 
-       switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref)) {
+       switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) {
        case 0:
                /* Not branching from any existing branch */
                if (explicit_tracking)
index 6ec82097a2a29c73d0b69a9329c93d8057dbb955..0951f8fee5cc2a88ac9e08009a69eaf446b6fed4 100644 (file)
@@ -651,7 +651,7 @@ static void setup_branch_path(struct branch_info *branch)
         * If this is a ref, resolve it; otherwise, look up the OID for our
         * expression.  Failure here is okay.
         */
-       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname))
+       if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname, 0))
                repo_get_oid_committish(the_repository, branch->name, &branch->oid);
 
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
@@ -1345,7 +1345,7 @@ static void die_expecting_a_branch(const struct branch_info *branch_info)
        struct object_id oid;
        char *to_free;
 
-       if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) {
+       if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) {
                const char *ref = to_free;
 
                if (skip_prefix(ref, "refs/tags/", &ref))
index 9f37895d4cf2ecb01c62e490be7a100ce5256398..1b8fca3ee000aa8f1fe49f407d538da09ac8a43b 100644 (file)
@@ -943,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info)
                if (e->flags & UNINTERESTING)
                        continue;
 
-               if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1)
                        continue;
 
                if (refspecs.nr) {
index 1c85eafe43eb2a4ed46b5aa2687eedf72fe61d54..1bf50a73dc3597394ecf73e5a786fe217e7ef633 100644 (file)
@@ -739,7 +739,6 @@ static void start_packfile(void)
 {
        struct strbuf tmp_file = STRBUF_INIT;
        struct packed_git *p;
-       struct pack_header hdr;
        int pack_fd;
 
        pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX");
@@ -750,13 +749,8 @@ static void start_packfile(void)
        p->do_not_close = 1;
        pack_file = hashfd(pack_fd, p->pack_name);
 
-       hdr.hdr_signature = htonl(PACK_SIGNATURE);
-       hdr.hdr_version = htonl(2);
-       hdr.hdr_entries = 0;
-       hashwrite(pack_file, &hdr, sizeof(hdr));
-
        pack_data = p;
-       pack_size = sizeof(hdr);
+       pack_size = write_pack_header(pack_file, 0);
        object_count = 0;
 
        REALLOC_ARRAY(all_packs, pack_id + 1);
index bbc9bc78f9d5a32ed9d167a79426ba0329bdea95..cd3e7605417dad82417294644b9f6de376792379 100644 (file)
@@ -9,6 +9,7 @@
 #include "builtin.h"
 #include "exec-cmd.h"
 #include "parse-options.h"
+#include "worktree.h"
 
 #ifndef DEFAULT_GIT_TEMPLATE_DIR
 #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
@@ -364,6 +365,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
 
                if (rename(src, git_dir))
                        die_errno(_("unable to move %s to %s"), src, git_dir);
+               repair_worktrees(NULL, NULL);
        }
 
        write_file(git_link, "gitdir: %s", git_dir);
@@ -640,6 +642,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 
+       /*
+        * When --separate-git-dir is used inside a linked worktree, take
+        * care to ensure that the common .git/ directory is relocated, not
+        * the worktree-specific .git/worktrees/<id>/ directory.
+        */
+       if (real_git_dir) {
+               int err;
+               const char *p;
+               struct strbuf sb = STRBUF_INIT;
+
+               p = read_gitfile_gently(git_dir, &err);
+               if (p && get_common_dir(&sb, p)) {
+                       struct strbuf mainwt = STRBUF_INIT;
+
+                       strbuf_addbuf(&mainwt, &sb);
+                       strbuf_strip_suffix(&mainwt, "/.git");
+                       if (chdir(mainwt.buf) < 0)
+                               die_errno(_("cannot chdir to %s"), mainwt.buf);
+                       strbuf_release(&mainwt);
+                       git_dir = strbuf_detach(&sb, NULL);
+               }
+               strbuf_release(&sb);
+       }
+
        if (is_bare_repository_cfg < 0)
                is_bare_repository_cfg = guess_repository_type(git_dir);
 
index b58f8da09ef7a16a1df0a99aba36003dc8732a24..4ec7f57cf4ac2adcb0cbc3abffa8769eb1273396 100644 (file)
@@ -1061,7 +1061,7 @@ static char *find_branch_name(struct rev_info *rev)
                return NULL;
        ref = rev->cmdline.rev[positive].name;
        tip_oid = &rev->cmdline.rev[positive].item->oid;
-       if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) &&
+       if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref, 0) &&
            skip_prefix(full_ref, "refs/heads/", &v) &&
            oideq(tip_oid, &branch_oid))
                branch = xstrdup(v);
index b9a89ba858a366a162ea21a54a63c857f70278c7..032a8f54344240fa7f727b3777df0d4f78e09b10 100644 (file)
@@ -500,7 +500,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
 
-       if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) {
+       if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
                                    oid_to_hex(&branch_head), remote);
index 04c5ceaf7ec7e6de32b21d4a07ebb8384ee3068f..01e7767c792866086ca889d7ea9d0310238c7c84 100644 (file)
@@ -133,7 +133,11 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
 static void remove_redundant_pack(const char *dir_name, const char *base_name)
 {
        struct strbuf buf = STRBUF_INIT;
-       strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name);
+       struct multi_pack_index *m = get_local_multi_pack_index(the_repository);
+       strbuf_addf(&buf, "%s.pack", base_name);
+       if (m && midx_contains_pack(m, buf.buf))
+               clear_midx_file(the_repository);
+       strbuf_insertf(&buf, 0, "%s/", dir_name);
        unlink_pack_path(buf.buf, 1);
        strbuf_release(&buf);
 }
@@ -286,7 +290,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        int keep_unreachable = 0;
        struct string_list keep_pack_list = STRING_LIST_INIT_NODUP;
        int no_update_server_info = 0;
-       int midx_cleared = 0;
        struct pack_objects_args po_args = {NULL};
 
        struct option builtin_repack_options[] = {
@@ -439,11 +442,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                for (ext = 0; ext < ARRAY_SIZE(exts); ext++) {
                        char *fname, *fname_old;
 
-                       if (!midx_cleared) {
-                               clear_midx_file(the_repository);
-                               midx_cleared = 1;
-                       }
-
                        fname = mkpathdup("%s/pack-%s%s", packdir,
                                                item->string, exts[ext].name);
                        if (!file_exists(fname)) {
index 8ae69d6f2b9e5e1760dd40d32631b0ecf48a31e6..c635b062c3a7b1cf2ad53b34f1471f5eeee83cce 100644 (file)
@@ -423,7 +423,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                        char *ref = NULL;
                        int err;
 
-                       dwim_ref(rev, strlen(rev), &dummy, &ref);
+                       dwim_ref(rev, strlen(rev), &dummy, &ref, 0);
                        if (ref && !starts_with(ref, "refs/"))
                                ref = NULL;
 
index 669dd2fd6f087628c3d7dc9ba165771825418799..ed200c8af1285ed4ac45a50aaa6275a739585836 100644 (file)
@@ -136,7 +136,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
                        struct object_id discard;
                        char *full;
 
-                       switch (dwim_ref(name, strlen(name), &discard, &full)) {
+                       switch (dwim_ref(name, strlen(name), &discard, &full, 0)) {
                        case 0:
                                /*
                                 * Not found -- not a ref.  We could
index 7eae5f38016dab4b0f4e9a4fdc7d496962389464..d6d2dabeca879638109a327eb4c96fbcbf0ecaf7 100644 (file)
@@ -741,7 +741,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        die(Q_("only %d entry can be shown at one time.",
                               "only %d entries can be shown at one time.",
                               MAX_REVS), MAX_REVS);
-               if (!dwim_ref(*av, strlen(*av), &oid, &ref))
+               if (!dwim_ref(*av, strlen(*av), &oid, &ref, 0))
                        die(_("no such ref %s"), *av);
 
                /* Has the base been specified? */
index 4bdfaf839705a69c58fa631055eb4fc8fdff0ed4..3f811f30506ab799f9bb4d1f37599c74175916d0 100644 (file)
@@ -185,7 +185,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
        end_of_rev = strchrnul(revision, '@');
        strbuf_add(&symbolic, revision, end_of_rev - revision);
 
-       ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref);
+       ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0);
        strbuf_release(&symbolic);
        switch (ret) {
        case 0: /* Not found, but valid ref */
index a59d8e4bda615cf843515da2312febdf2c72c213..de5ad73bb8d51209d36a01247ba67b233a54f944 100644 (file)
@@ -612,7 +612,6 @@ struct init_cb {
        const char *prefix;
        unsigned int flags;
 };
-
 #define INIT_CB_INIT { NULL, 0 }
 
 static void init_submodule(const char *path, const char *prefix,
@@ -742,7 +741,6 @@ struct status_cb {
        const char *prefix;
        unsigned int flags;
 };
-
 #define STATUS_CB_INIT { NULL, 0 }
 
 static void print_status(unsigned int flags, char state, const char *path,
@@ -929,11 +927,437 @@ static int module_name(int argc, const char **argv, const char *prefix)
        return 0;
 }
 
+struct module_cb {
+       unsigned int mod_src;
+       unsigned int mod_dst;
+       struct object_id oid_src;
+       struct object_id oid_dst;
+       char status;
+       const char *sm_path;
+};
+#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL }
+
+struct module_cb_list {
+       struct module_cb **entries;
+       int alloc, nr;
+};
+#define MODULE_CB_LIST_INIT { NULL, 0, 0 }
+
+struct summary_cb {
+       int argc;
+       const char **argv;
+       const char *prefix;
+       unsigned int cached: 1;
+       unsigned int for_status: 1;
+       unsigned int files: 1;
+       int summary_limit;
+};
+#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 }
+
+enum diff_cmd {
+       DIFF_INDEX,
+       DIFF_FILES
+};
+
+static char *verify_submodule_committish(const char *sm_path,
+                                        const char *committish)
+{
+       struct child_process cp_rev_parse = CHILD_PROCESS_INIT;
+       struct strbuf result = STRBUF_INIT;
+
+       cp_rev_parse.git_cmd = 1;
+       cp_rev_parse.dir = sm_path;
+       prepare_submodule_repo_env(&cp_rev_parse.env_array);
+       strvec_pushl(&cp_rev_parse.args, "rev-parse", "-q", "--short", NULL);
+       strvec_pushf(&cp_rev_parse.args, "%s^0", committish);
+       strvec_push(&cp_rev_parse.args, "--");
+
+       if (capture_command(&cp_rev_parse, &result, 0))
+               return NULL;
+
+       strbuf_trim_trailing_newline(&result);
+       return strbuf_detach(&result, NULL);
+}
+
+static void print_submodule_summary(struct summary_cb *info, char *errmsg,
+                                   int total_commits, const char *displaypath,
+                                   const char *src_abbrev, const char *dst_abbrev,
+                                   struct module_cb *p)
+{
+       if (p->status == 'T') {
+               if (S_ISGITLINK(p->mod_dst))
+                       printf(_("* %s %s(blob)->%s(submodule)"),
+                                displaypath, src_abbrev, dst_abbrev);
+               else
+                       printf(_("* %s %s(submodule)->%s(blob)"),
+                                displaypath, src_abbrev, dst_abbrev);
+       } else {
+               printf("* %s %s...%s",
+                       displaypath, src_abbrev, dst_abbrev);
+       }
+
+       if (total_commits < 0)
+               printf(":\n");
+       else
+               printf(" (%d):\n", total_commits);
+
+       if (errmsg) {
+               printf(_("%s"), errmsg);
+       } else if (total_commits > 0) {
+               struct child_process cp_log = CHILD_PROCESS_INIT;
+
+               cp_log.git_cmd = 1;
+               cp_log.dir = p->sm_path;
+               prepare_submodule_repo_env(&cp_log.env_array);
+               strvec_pushl(&cp_log.args, "log", NULL);
+
+               if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) {
+                       if (info->summary_limit > 0)
+                               strvec_pushf(&cp_log.args, "-%d",
+                                            info->summary_limit);
+
+                       strvec_pushl(&cp_log.args, "--pretty=  %m %s",
+                                    "--first-parent", NULL);
+                       strvec_pushf(&cp_log.args, "%s...%s",
+                                    src_abbrev, dst_abbrev);
+               } else if (S_ISGITLINK(p->mod_dst)) {
+                       strvec_pushl(&cp_log.args, "--pretty=  > %s",
+                                    "-1", dst_abbrev, NULL);
+               } else {
+                       strvec_pushl(&cp_log.args, "--pretty=  < %s",
+                                    "-1", src_abbrev, NULL);
+               }
+               run_command(&cp_log);
+       }
+       printf("\n");
+}
+
+static void generate_submodule_summary(struct summary_cb *info,
+                                      struct module_cb *p)
+{
+       char *displaypath, *src_abbrev = NULL, *dst_abbrev;
+       int missing_src = 0, missing_dst = 0;
+       char *errmsg = NULL;
+       int total_commits = -1;
+
+       if (!info->cached && oideq(&p->oid_dst, &null_oid)) {
+               if (S_ISGITLINK(p->mod_dst)) {
+                       struct ref_store *refs = get_submodule_ref_store(p->sm_path);
+                       if (refs)
+                               refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst);
+               } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) {
+                       struct stat st;
+                       int fd = open(p->sm_path, O_RDONLY);
+
+                       if (fd < 0 || fstat(fd, &st) < 0 ||
+                           index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB,
+                                    p->sm_path, 0))
+                               error(_("couldn't hash object from '%s'"), p->sm_path);
+               } else {
+                       /* for a submodule removal (mode:0000000), don't warn */
+                       if (p->mod_dst)
+                               warning(_("unexpected mode %o\n"), p->mod_dst);
+               }
+       }
+
+       if (S_ISGITLINK(p->mod_src)) {
+               if (p->status != 'D')
+                       src_abbrev = verify_submodule_committish(p->sm_path,
+                                                                oid_to_hex(&p->oid_src));
+               if (!src_abbrev) {
+                       missing_src = 1;
+                       /*
+                        * As `rev-parse` failed, we fallback to getting
+                        * the abbreviated hash using oid_src. We do
+                        * this as we might still need the abbreviated
+                        * hash in cases like a submodule type change, etc.
+                        */
+                       src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+               }
+       } else {
+               /*
+                * The source does not point to a submodule.
+                * So, we fallback to getting the abbreviation using
+                * oid_src as we might still need the abbreviated
+                * hash in cases like submodule add, etc.
+                */
+               src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7);
+       }
+
+       if (S_ISGITLINK(p->mod_dst)) {
+               dst_abbrev = verify_submodule_committish(p->sm_path,
+                                                        oid_to_hex(&p->oid_dst));
+               if (!dst_abbrev) {
+                       missing_dst = 1;
+                       /*
+                        * As `rev-parse` failed, we fallback to getting
+                        * the abbreviated hash using oid_dst. We do
+                        * this as we might still need the abbreviated
+                        * hash in cases like a submodule type change, etc.
+                        */
+                       dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+               }
+       } else {
+               /*
+                * The destination does not point to a submodule.
+                * So, we fallback to getting the abbreviation using
+                * oid_dst as we might still need the abbreviated
+                * hash in cases like a submodule removal, etc.
+                */
+               dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7);
+       }
+
+       displaypath = get_submodule_displaypath(p->sm_path, info->prefix);
+
+       if (!missing_src && !missing_dst) {
+               struct child_process cp_rev_list = CHILD_PROCESS_INIT;
+               struct strbuf sb_rev_list = STRBUF_INIT;
+
+               strvec_pushl(&cp_rev_list.args, "rev-list",
+                            "--first-parent", "--count", NULL);
+               if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst))
+                       strvec_pushf(&cp_rev_list.args, "%s...%s",
+                                    src_abbrev, dst_abbrev);
+               else
+                       strvec_push(&cp_rev_list.args, S_ISGITLINK(p->mod_src) ?
+                                   src_abbrev : dst_abbrev);
+               strvec_push(&cp_rev_list.args, "--");
+
+               cp_rev_list.git_cmd = 1;
+               cp_rev_list.dir = p->sm_path;
+               prepare_submodule_repo_env(&cp_rev_list.env_array);
+
+               if (!capture_command(&cp_rev_list, &sb_rev_list, 0))
+                       total_commits = atoi(sb_rev_list.buf);
+
+               strbuf_release(&sb_rev_list);
+       } else {
+               /*
+                * Don't give error msg for modification whose dst is not
+                * submodule, i.e., deleted or changed to blob
+                */
+               if (S_ISGITLINK(p->mod_dst)) {
+                       struct strbuf errmsg_str = STRBUF_INIT;
+                       if (missing_src && missing_dst) {
+                               strbuf_addf(&errmsg_str, "  Warn: %s doesn't contain commits %s and %s\n",
+                                           displaypath, oid_to_hex(&p->oid_src),
+                                           oid_to_hex(&p->oid_dst));
+                       } else {
+                               strbuf_addf(&errmsg_str, "  Warn: %s doesn't contain commit %s\n",
+                                           displaypath, missing_src ?
+                                           oid_to_hex(&p->oid_src) :
+                                           oid_to_hex(&p->oid_dst));
+                       }
+                       errmsg = strbuf_detach(&errmsg_str, NULL);
+               }
+       }
+
+       print_submodule_summary(info, errmsg, total_commits,
+                               displaypath, src_abbrev,
+                               dst_abbrev, p);
+
+       free(displaypath);
+       free(src_abbrev);
+       free(dst_abbrev);
+}
+
+static void prepare_submodule_summary(struct summary_cb *info,
+                                     struct module_cb_list *list)
+{
+       int i;
+       for (i = 0; i < list->nr; i++) {
+               const struct submodule *sub;
+               struct module_cb *p = list->entries[i];
+               struct strbuf sm_gitdir = STRBUF_INIT;
+
+               if (p->status == 'D' || p->status == 'T') {
+                       generate_submodule_summary(info, p);
+                       continue;
+               }
+
+               if (info->for_status && p->status != 'A' &&
+                   (sub = submodule_from_path(the_repository,
+                                              &null_oid, p->sm_path))) {
+                       char *config_key = NULL;
+                       const char *value;
+                       int ignore_all = 0;
+
+                       config_key = xstrfmt("submodule.%s.ignore",
+                                            sub->name);
+                       if (!git_config_get_string_tmp(config_key, &value))
+                               ignore_all = !strcmp(value, "all");
+                       else if (sub->ignore)
+                               ignore_all = !strcmp(sub->ignore, "all");
+
+                       free(config_key);
+                       if (ignore_all)
+                               continue;
+               }
+
+               /* Also show added or modified modules which are checked out */
+               strbuf_addstr(&sm_gitdir, p->sm_path);
+               if (is_nonbare_repository_dir(&sm_gitdir))
+                       generate_submodule_summary(info, p);
+               strbuf_release(&sm_gitdir);
+       }
+}
+
+static void submodule_summary_callback(struct diff_queue_struct *q,
+                                      struct diff_options *options,
+                                      void *data)
+{
+       int i;
+       struct module_cb_list *list = data;
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               struct module_cb *temp;
+
+               if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode))
+                       continue;
+               temp = (struct module_cb*)malloc(sizeof(struct module_cb));
+               temp->mod_src = p->one->mode;
+               temp->mod_dst = p->two->mode;
+               temp->oid_src = p->one->oid;
+               temp->oid_dst = p->two->oid;
+               temp->status = p->status;
+               temp->sm_path = xstrdup(p->one->path);
+
+               ALLOC_GROW(list->entries, list->nr + 1, list->alloc);
+               list->entries[list->nr++] = temp;
+       }
+}
+
+static const char *get_diff_cmd(enum diff_cmd diff_cmd)
+{
+       switch (diff_cmd) {
+       case DIFF_INDEX: return "diff-index";
+       case DIFF_FILES: return "diff-files";
+       default: BUG("bad diff_cmd value %d", diff_cmd);
+       }
+}
+
+static int compute_summary_module_list(struct object_id *head_oid,
+                                      struct summary_cb *info,
+                                      enum diff_cmd diff_cmd)
+{
+       struct strvec diff_args = STRVEC_INIT;
+       struct rev_info rev;
+       struct module_cb_list list = MODULE_CB_LIST_INIT;
+
+       strvec_push(&diff_args, get_diff_cmd(diff_cmd));
+       if (info->cached)
+               strvec_push(&diff_args, "--cached");
+       strvec_pushl(&diff_args, "--ignore-submodules=dirty", "--raw", NULL);
+       if (head_oid)
+               strvec_push(&diff_args, oid_to_hex(head_oid));
+       strvec_push(&diff_args, "--");
+       if (info->argc)
+               strvec_pushv(&diff_args, info->argv);
+
+       git_config(git_diff_basic_config, NULL);
+       init_revisions(&rev, info->prefix);
+       rev.abbrev = 0;
+       precompose_argv(diff_args.nr, diff_args.v);
+       setup_revisions(diff_args.nr, diff_args.v, &rev, NULL);
+       rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK;
+       rev.diffopt.format_callback = submodule_summary_callback;
+       rev.diffopt.format_callback_data = &list;
+
+       if (!info->cached) {
+               if (diff_cmd == DIFF_INDEX)
+                       setup_work_tree();
+               if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
+                       perror("read_cache_preload");
+                       return -1;
+               }
+       } else if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
+       }
+
+       if (diff_cmd == DIFF_INDEX)
+               run_diff_index(&rev, info->cached);
+       else
+               run_diff_files(&rev, 0);
+       prepare_submodule_summary(info, &list);
+       strvec_clear(&diff_args);
+       return 0;
+}
+
+static int module_summary(int argc, const char **argv, const char *prefix)
+{
+       struct summary_cb info = SUMMARY_CB_INIT;
+       int cached = 0;
+       int for_status = 0;
+       int files = 0;
+       int summary_limit = -1;
+       enum diff_cmd diff_cmd = DIFF_INDEX;
+       struct object_id head_oid;
+       int ret;
+
+       struct option module_summary_options[] = {
+               OPT_BOOL(0, "cached", &cached,
+                        N_("use the commit stored in the index instead of the submodule HEAD")),
+               OPT_BOOL(0, "files", &files,
+                        N_("to compare the commit in the index with that in the submodule HEAD")),
+               OPT_BOOL(0, "for-status", &for_status,
+                        N_("skip submodules with 'ignore_config' value set to 'all'")),
+               OPT_INTEGER('n', "summary-limit", &summary_limit,
+                            N_("limit the summary size")),
+               OPT_END()
+       };
+
+       const char *const git_submodule_helper_usage[] = {
+               N_("git submodule--helper summary [<options>] [commit] [--] [<path>]"),
+               NULL
+       };
+
+       argc = parse_options(argc, argv, prefix, module_summary_options,
+                            git_submodule_helper_usage, 0);
+
+       if (!summary_limit)
+               return 0;
+
+       if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) {
+               if (argc) {
+                       argv++;
+                       argc--;
+               }
+       } else if (!argc || !strcmp(argv[0], "HEAD")) {
+               /* before the first commit: compare with an empty tree */
+               oidcpy(&head_oid, the_hash_algo->empty_tree);
+               if (argc) {
+                       argv++;
+                       argc--;
+               }
+       } else {
+               if (get_oid("HEAD", &head_oid))
+                       die(_("could not fetch a revision for HEAD"));
+       }
+
+       if (files) {
+               if (cached)
+                       die(_("--cached and --files are mutually exclusive"));
+               diff_cmd = DIFF_FILES;
+       }
+
+       info.argc = argc;
+       info.argv = argv;
+       info.prefix = prefix;
+       info.cached = !!cached;
+       info.files = !!files;
+       info.for_status = !!for_status;
+       info.summary_limit = summary_limit;
+
+       ret = compute_summary_module_list((diff_cmd == DIFF_INDEX) ? &head_oid : NULL,
+                                         &info, diff_cmd);
+       return ret;
+}
+
 struct sync_cb {
        const char *prefix;
        unsigned int flags;
 };
-
 #define SYNC_CB_INIT { NULL, 0 }
 
 static void sync_submodule(const char *path, const char *prefix,
@@ -2344,6 +2768,7 @@ static struct cmd_struct commands[] = {
        {"print-default-remote", print_default_remote, 0},
        {"sync", module_sync, SUPPORT_SUPER_PREFIX},
        {"deinit", module_deinit, 0},
+       {"summary", module_summary, SUPPORT_SUPER_PREFIX},
        {"remote-branch", resolve_remote_submodule_branch, 0},
        {"push-check", push_check, 0},
        {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX},
index 378f332b5d07edb55b48559ba328f980b84927e6..bb70fde97e218580a6e0d5f39dbd00458196bfee 100644 (file)
@@ -924,7 +924,6 @@ static int move_worktree(int ac, const char **av, const char *prefix)
 static void check_clean_worktree(struct worktree *wt,
                                 const char *original_path)
 {
-       struct strvec child_env = STRVEC_INIT;
        struct child_process cp;
        char buf[1];
        int ret;
@@ -935,15 +934,14 @@ static void check_clean_worktree(struct worktree *wt,
         */
        validate_no_submodules(wt);
 
-       strvec_pushf(&child_env, "%s=%s/.git",
+       child_process_init(&cp);
+       strvec_pushf(&cp.env_array, "%s=%s/.git",
                     GIT_DIR_ENVIRONMENT, wt->path);
-       strvec_pushf(&child_env, "%s=%s",
+       strvec_pushf(&cp.env_array, "%s=%s",
                     GIT_WORK_TREE_ENVIRONMENT, wt->path);
-       memset(&cp, 0, sizeof(cp));
        strvec_pushl(&cp.args, "status",
                     "--porcelain", "--ignore-submodules=none",
                     NULL);
-       cp.env = child_env.v;
        cp.git_cmd = 1;
        cp.dir = wt->path;
        cp.out = -1;
@@ -1030,6 +1028,34 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
        return ret;
 }
 
+static void report_repair(int iserr, const char *path, const char *msg, void *cb_data)
+{
+       if (!iserr) {
+               printf_ln(_("repair: %s: %s"), msg, path);
+       } else {
+               int *exit_status = (int *)cb_data;
+               fprintf_ln(stderr, _("error: %s: %s"), msg, path);
+               *exit_status = 1;
+       }
+}
+
+static int repair(int ac, const char **av, const char *prefix)
+{
+       const char **p;
+       const char *self[] = { ".", NULL };
+       struct option options[] = {
+               OPT_END()
+       };
+       int rc = 0;
+
+       ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+       repair_worktrees(report_repair, &rc);
+       p = ac > 0 ? av : self;
+       for (; *p; p++)
+               repair_worktree_at_path(*p, report_repair, &rc);
+       return rc;
+}
+
 int cmd_worktree(int ac, const char **av, const char *prefix)
 {
        struct option options[] = {
@@ -1056,5 +1082,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
                return move_worktree(ac - 1, av + 1, prefix);
        if (!strcmp(av[1], "remove"))
                return remove_worktree(ac - 1, av + 1, prefix);
+       if (!strcmp(av[1], "repair"))
+               return repair(ac - 1, av + 1, prefix);
        usage_with_options(worktree_usage, options);
 }
index 995a940dfd65864bb88251d486f37d67ddae728e..cb0e5931ac78ecc4b3525907d46464cf6f771118 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -403,7 +403,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
 
                if (e->item->flags & UNINTERESTING)
                        continue;
-               if (dwim_ref(e->name, strlen(e->name), &oid, &ref) != 1)
+               if (dwim_ref(e->name, strlen(e->name), &oid, &ref, 0) != 1)
                        goto skip_write_ref;
                if (read_ref_full(e->name, RESOLVE_REF_READING, &oid, &flag))
                        flag = 0;
diff --git a/cache.h b/cache.h
index 4cad61ffa4eecf4e793971a4009d199f7c2f3a50..cee8aa5dc325a422ac6ba7a7795fb05afd8ae642 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1557,21 +1557,32 @@ int parse_oid_hex_any(const char *hex, struct object_id *oid, const char **end);
  *
  * If the input was ok but there are not N branch switches in the
  * reflog, it returns 0.
- *
- * If "allowed" is non-zero, it is a treated as a bitfield of allowable
- * expansions: local branches ("refs/heads/"), remote branches
- * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
- * allowed, even ones to refs outside of those namespaces.
  */
 #define INTERPRET_BRANCH_LOCAL (1<<0)
 #define INTERPRET_BRANCH_REMOTE (1<<1)
 #define INTERPRET_BRANCH_HEAD (1<<2)
+struct interpret_branch_name_options {
+       /*
+        * If "allowed" is non-zero, it is a treated as a bitfield of allowable
+        * expansions: local branches ("refs/heads/"), remote branches
+        * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
+        * allowed, even ones to refs outside of those namespaces.
+        */
+       unsigned allowed;
+
+       /*
+        * If ^{upstream} or ^{push} (or equivalent) is requested, and the
+        * branch in question does not have such a reference, return -1 instead
+        * of die()-ing.
+        */
+       unsigned nonfatal_dangling_mark : 1;
+};
 int repo_interpret_branch_name(struct repository *r,
                               const char *str, int len,
                               struct strbuf *buf,
-                              unsigned allowed);
-#define interpret_branch_name(str, len, buf, allowed) \
-       repo_interpret_branch_name(the_repository, str, len, buf, allowed)
+                              const struct interpret_branch_name_options *options);
+#define interpret_branch_name(str, len, buf, options) \
+       repo_interpret_branch_name(the_repository, str, len, buf, options)
 
 int validate_headref(const char *ref);
 
index bc741e78ad7dc09eab6c7f24d56377ec1397040e..f53429c0ac34d933a06f6051cd6b5e519408e4be 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -921,7 +921,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
        struct commit *ret = NULL;
        char *full_refname;
 
-       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname)) {
+       switch (dwim_ref(refname, strlen(refname), &oid, &full_refname, 0)) {
        case 0:
                die("No such ref: '%s'", refname);
        case 1:
index 42292e7c098e23e61f5f7e602b46ddae335da9e9..51fb083dbbe213351bab20a4352013aa5a69a6dd 100644 (file)
@@ -26,8 +26,8 @@ The Steps to Build Git with VS2015 or VS2017 from the command line.
    Use ONE of the following forms which should match how you want to
    compile git.exe.
 
-   $ ./compat/vcbuild/vcpkg_copy_packages.bat debug
-   $ ./compat/vcbuild/vcpkg_copy_packages.bat release
+   $ ./compat/vcbuild/vcpkg_copy_dlls.bat debug
+   $ ./compat/vcbuild/vcpkg_copy_dlls.bat release
 
 3. Build git using MSVC from an SDK bash window using one of the
    following commands:
index 61ad084a7b710e3b667fe191bd06a63879e5177e..df167d1e1a542d37c794f0ed307dfdaac89092e3 100755 (executable)
@@ -66,7 +66,7 @@ while (@ARGV) {
                }
                push(@args, $lib);
        } elsif ("$arg" eq "-lexpat") {
-               push(@args, "expat.lib");
+               push(@args, "libexpat.lib");
        } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
                $arg =~ s/^-L/-LIBPATH:/;
                push(@lflags, $arg);
index 5c666f9ac03b01f968d8fb36e064a47c6fb50a4a..d2584450ba1723861162ea32f93e6a6e5f7b4f4f 100644 (file)
@@ -80,6 +80,7 @@ sub createProject {
       $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
       $libs_debug = $libs_release;
       $libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
+      $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g;
       $libs_debug =~ s/libcurl\.lib/libcurl-d\.lib/g;
     }
 
index 070978506ad533b82f72aed9bd4f4062ee88cc71..2ff96204596445345e4566f37427c5ca49111419 100755 (executable)
@@ -349,7 +349,7 @@ sub handleLinkLine
         } elsif ("$part" eq "-lcurl") {
             push(@libs, "libcurl.lib");
         } elsif ("$part" eq "-lexpat") {
-            push(@libs, "expat.lib");
+            push(@libs, "libexpat.lib");
         } elsif ("$part" eq "-liconv") {
             push(@libs, "libiconv.lib");
         } elsif ($part =~ /^[-\/]/) {
index 50521e2093fc581ce3cedc76b3036a61f31bef35..5d5d3dafab3386920d2c0883d33d06f860f6d11b 100644 (file)
@@ -405,14 +405,8 @@ static void do_oneway_diff(struct unpack_trees_options *o,
        /* if the entry is not checked out, don't examine work tree */
        cached = o->index_only ||
                (idx && ((idx->ce_flags & CE_VALID) || ce_skip_worktree(idx)));
-       /*
-        * Backward compatibility wart - "diff-index -m" does
-        * not mean "do not ignore merges", but "match_missing".
-        *
-        * But with the revision flag parsing, that's found in
-        * "!revs->ignore_merges".
-        */
-       match_missing = !revs->ignore_merges;
+
+       match_missing = revs->match_missing;
 
        if (cached && idx && ce_stage(idx)) {
                struct diff_filepair *pair;
diff --git a/diff.c b/diff.c
index 0299a730795185db71341f5d7b277205604cac59..a5114fa86468ceff56005ed83fcb10f129dc0fc2 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3432,7 +3432,7 @@ static void builtin_diff(const char *name_a,
        if (o->submodule_format == DIFF_SUBMODULE_LOG &&
            (!one->mode || S_ISGITLINK(one->mode)) &&
            (!two->mode || S_ISGITLINK(two->mode))) {
-               show_submodule_summary(o, one->path ? one->path : two->path,
+               show_submodule_diff_summary(o, one->path ? one->path : two->path,
                                &one->oid, &two->oid,
                                two->dirty_submodule);
                return;
index 43eb6051d23f90cccc94c1fb4c58ad14d4ece3e6..6fb12585cb07c3eb1f0a9ff1263c8d51de26962c 100755 (executable)
@@ -59,31 +59,6 @@ die_if_unmatched ()
        fi
 }
 
-#
-# Print a submodule configuration setting
-#
-# $1 = submodule name
-# $2 = option name
-# $3 = default value
-#
-# Checks in the usual git-config places first (for overrides),
-# otherwise it falls back on .gitmodules.  This allows you to
-# distribute project-wide defaults in .gitmodules, while still
-# customizing individual repositories if necessary.  If the option is
-# not in .gitmodules either, print a default value.
-#
-get_submodule_config () {
-       name="$1"
-       option="$2"
-       default="$3"
-       value=$(git config submodule."$name"."$option")
-       if test -z "$value"
-       then
-               value=$(git submodule--helper config submodule."$name"."$option")
-       fi
-       printf '%s' "${value:-$default}"
-}
-
 isnumber()
 {
        n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1"
@@ -831,166 +806,7 @@ cmd_summary() {
                shift
        done
 
-       test $summary_limit = 0 && return
-
-       if rev=$(git rev-parse -q --verify --default HEAD ${1+"$1"})
-       then
-               head=$rev
-               test $# = 0 || shift
-       elif test -z "$1" || test "$1" = "HEAD"
-       then
-               # before the first commit: compare with an empty tree
-               head=$(git hash-object -w -t tree --stdin </dev/null)
-               test -z "$1" || shift
-       else
-               head="HEAD"
-       fi
-
-       if [ -n "$files" ]
-       then
-               test -n "$cached" &&
-               die "$(gettext "The --cached option cannot be used with the --files option")"
-               diff_cmd=diff-files
-               head=
-       fi
-
-       cd_to_toplevel
-       eval "set $(git rev-parse --sq --prefix "$wt_prefix" -- "$@")"
-       # Get modified modules cared by user
-       modules=$(git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- "$@" |
-               sane_egrep '^:([0-7]* )?160000' |
-               while read -r mod_src mod_dst sha1_src sha1_dst status sm_path
-               do
-                       # Always show modules deleted or type-changed (blob<->module)
-                       if test "$status" = D || test "$status" = T
-                       then
-                               printf '%s\n' "$sm_path"
-                               continue
-                       fi
-                       # Respect the ignore setting for --for-status.
-                       if test -n "$for_status"
-                       then
-                               name=$(git submodule--helper name "$sm_path")
-                               ignore_config=$(get_submodule_config "$name" ignore none)
-                               test $status != A && test $ignore_config = all && continue
-                       fi
-                       # Also show added or modified modules which are checked out
-                       GIT_DIR="$sm_path/.git" git rev-parse --git-dir >/dev/null 2>&1 &&
-                       printf '%s\n' "$sm_path"
-               done
-       )
-
-       test -z "$modules" && return
-
-       git $diff_cmd $cached --ignore-submodules=dirty --raw $head -- $modules |
-       sane_egrep '^:([0-7]* )?160000' |
-       cut -c2- |
-       while read -r mod_src mod_dst sha1_src sha1_dst status name
-       do
-               if test -z "$cached" &&
-                       is_zero_oid $sha1_dst
-               then
-                       case "$mod_dst" in
-                       160000)
-                               sha1_dst=$(GIT_DIR="$name/.git" git rev-parse HEAD)
-                               ;;
-                       100644 | 100755 | 120000)
-                               sha1_dst=$(git hash-object $name)
-                               ;;
-                       000000)
-                               ;; # removed
-                       *)
-                               # unexpected type
-                               eval_gettextln "unexpected mode \$mod_dst" >&2
-                               continue ;;
-                       esac
-               fi
-               missing_src=
-               missing_dst=
-
-               test $mod_src = 160000 &&
-               ! GIT_DIR="$name/.git" git rev-parse -q --verify $sha1_src^0 >/dev/null &&
-               missing_src=t
-
-               test $mod_dst = 160000 &&
-               ! GIT_DIR="$name/.git" git rev-parse -q --verify $sha1_dst^0 >/dev/null &&
-               missing_dst=t
-
-               display_name=$(git submodule--helper relative-path "$name" "$wt_prefix")
-
-               total_commits=
-               case "$missing_src,$missing_dst" in
-               t,)
-                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_src")"
-                       ;;
-               ,t)
-                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commit \$sha1_dst")"
-                       ;;
-               t,t)
-                       errmsg="$(eval_gettext "  Warn: \$display_name doesn't contain commits \$sha1_src and \$sha1_dst")"
-                       ;;
-               *)
-                       errmsg=
-                       total_commits=$(
-                       if test $mod_src = 160000 && test $mod_dst = 160000
-                       then
-                               range="$sha1_src...$sha1_dst"
-                       elif test $mod_src = 160000
-                       then
-                               range=$sha1_src
-                       else
-                               range=$sha1_dst
-                       fi
-                       GIT_DIR="$name/.git" \
-                       git rev-list --first-parent $range -- | wc -l
-                       )
-                       total_commits=" ($(($total_commits + 0)))"
-                       ;;
-               esac
-
-               sha1_abbr_src=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_src 2>/dev/null ||
-                       echo $sha1_src | cut -c1-7)
-               sha1_abbr_dst=$(GIT_DIR="$name/.git" git rev-parse --short $sha1_dst 2>/dev/null ||
-                       echo $sha1_dst | cut -c1-7)
-
-               if test $status = T
-               then
-                       blob="$(gettext "blob")"
-                       submodule="$(gettext "submodule")"
-                       if test $mod_dst = 160000
-                       then
-                               echo "* $display_name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:"
-                       else
-                               echo "* $display_name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:"
-                       fi
-               else
-                       echo "* $display_name $sha1_abbr_src...$sha1_abbr_dst$total_commits:"
-               fi
-               if test -n "$errmsg"
-               then
-                       # Don't give error msg for modification whose dst is not submodule
-                       # i.e. deleted or changed to blob
-                       test $mod_dst = 160000 && echo "$errmsg"
-               else
-                       if test $mod_src = 160000 && test $mod_dst = 160000
-                       then
-                               limit=
-                               test $summary_limit -gt 0 && limit="-$summary_limit"
-                               GIT_DIR="$name/.git" \
-                               git log $limit --pretty='format:  %m %s' \
-                               --first-parent $sha1_src...$sha1_dst
-                       elif test $mod_dst = 160000
-                       then
-                               GIT_DIR="$name/.git" \
-                               git log --pretty='format:  > %s' -1 $sha1_dst
-                       else
-                               GIT_DIR="$name/.git" \
-                               git log --pretty='format:  < %s' -1 $sha1_src
-                       fi
-                       echo
-               fi
-               echo
-       done
+       git ${wt_prefix:+-C "$wt_prefix"} submodule--helper summary ${prefix:+--prefix "$prefix"} ${files:+--files} ${cached:+--cached} ${for_status:+--for-status} ${summary_limit:+-n $summary_limit} -- "$@"
 }
 #
 # List all submodules, prefixed with:
diff --git a/midx.c b/midx.c
index e9b2e1253a678836dab68a1f15cdaf954444cfea..0de42ffdfb22c630e5b28f4997aa558c01a44d79 100644 (file)
--- a/midx.c
+++ b/midx.c
@@ -416,8 +416,12 @@ int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, i
        m = load_multi_pack_index(object_dir, local);
 
        if (m) {
-               m->next = r->objects->multi_pack_index;
-               r->objects->multi_pack_index = m;
+               struct multi_pack_index *mp = r->objects->multi_pack_index;
+               if (mp) {
+                       m->next = mp->next;
+                       mp->next = m;
+               } else
+                       r->objects->multi_pack_index = m;
                return 1;
        }
 
@@ -428,14 +432,11 @@ static size_t write_midx_header(struct hashfile *f,
                                unsigned char num_chunks,
                                uint32_t num_packs)
 {
-       unsigned char byte_values[4];
-
        hashwrite_be32(f, MIDX_SIGNATURE);
-       byte_values[0] = MIDX_VERSION;
-       byte_values[1] = oid_version();
-       byte_values[2] = num_chunks;
-       byte_values[3] = 0; /* unused */
-       hashwrite(f, byte_values, sizeof(byte_values));
+       hashwrite_u8(f, MIDX_VERSION);
+       hashwrite_u8(f, oid_version());
+       hashwrite_u8(f, num_chunks);
+       hashwrite_u8(f, 0); /* unused */
        hashwrite_be32(f, num_packs);
 
        return MIDX_HEADER_SIZE;
index a7a4964b50d1936be0d769724c1b0b5fa5420672..5e998bdaa7998817a4dc73e5d6da711e0615b992 100644 (file)
@@ -503,8 +503,7 @@ static void write_hash_cache(struct hashfile *f,
 
        for (i = 0; i < index_nr; ++i) {
                struct object_entry *entry = (struct object_entry *)index[i];
-               uint32_t hash_value = htonl(entry->hash);
-               hashwrite(f, &hash_value, sizeof(hash_value));
+               hashwrite_be32(f, entry->hash);
        }
 }
 
index 6ab5233613e2417f8ee9ce0991ae532726b59b20..9ef27508f2ed6d2685e5d85cd92667ac735140d5 100644 (file)
@@ -1027,6 +1027,17 @@ struct multi_pack_index *get_multi_pack_index(struct repository *r)
        return r->objects->multi_pack_index;
 }
 
+struct multi_pack_index *get_local_multi_pack_index(struct repository *r)
+{
+       struct multi_pack_index *m = get_multi_pack_index(r);
+
+       /* no need to iterate; we always put the local one first (if any) */
+       if (m && m->local)
+               return m;
+
+       return NULL;
+}
+
 struct packed_git *get_all_packs(struct repository *r)
 {
        struct multi_pack_index *m;
index 240aa73b95a64912fb2cde5be049f57d91e53b84..a58fc738e06319624b666c80e2636217400cdda9 100644 (file)
@@ -57,6 +57,7 @@ void install_packed_git(struct repository *r, struct packed_git *pack);
 struct packed_git *get_packed_git(struct repository *r);
 struct list_head *get_packed_git_mru(struct repository *r);
 struct multi_pack_index *get_multi_pack_index(struct repository *r);
+struct multi_pack_index *get_local_multi_pack_index(struct repository *r);
 struct packed_git *get_all_packs(struct repository *r);
 
 /*
index 2a3d46bf42fea1d375eb1b29e52ee1caaa0372a0..7a7708a0ea707ac58f1961a65b40fa89ea8631d3 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -839,21 +839,22 @@ static int istitlechar(char c)
                (c >= '0' && c <= '9') || c == '.' || c == '_';
 }
 
-static void format_sanitized_subject(struct strbuf *sb, const char *msg)
+void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len)
 {
        size_t trimlen;
        size_t start_len = sb->len;
        int space = 2;
+       int i;
 
-       for (; *msg && *msg != '\n'; msg++) {
-               if (istitlechar(*msg)) {
+       for (i = 0; i < len; i++) {
+               if (istitlechar(msg[i])) {
                        if (space == 1)
                                strbuf_addch(sb, '-');
                        space = 0;
-                       strbuf_addch(sb, *msg);
-                       if (*msg == '.')
-                               while (*(msg+1) == '.')
-                                       msg++;
+                       strbuf_addch(sb, msg[i]);
+                       if (msg[i] == '.')
+                               while (msg[i+1] == '.')
+                                       i++;
                } else
                        space |= 1;
        }
@@ -1155,7 +1156,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
        const struct commit *commit = c->commit;
        const char *msg = c->message;
        struct commit_list *p;
-       const char *arg;
+       const char *arg, *eol;
        size_t res;
        char **slot;
 
@@ -1405,7 +1406,8 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                format_subject(sb, msg + c->subject_off, " ");
                return 1;
        case 'f':       /* sanitized subject */
-               format_sanitized_subject(sb, msg + c->subject_off);
+               eol = strchrnul(msg + c->subject_off, '\n');
+               format_sanitized_subject(sb, msg + c->subject_off, eol - (msg + c->subject_off));
                return 1;
        case 'b':       /* body */
                strbuf_addstr(sb, msg + c->body_off);
index 071f2fb8e449cee51c6556998b416c4fa7c8005a..7ce6c0b437b448b7f384ac3680de5c3e8983d8fb 100644 (file)
--- a/pretty.h
+++ b/pretty.h
@@ -139,4 +139,7 @@ const char *format_subject(struct strbuf *sb, const char *msg,
 /* Check if "cmit_fmt" will produce an empty output. */
 int commit_format_is_empty(enum cmit_fmt);
 
+/* Make subject of commit message suitable for filename */
+void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len);
+
 #endif /* PRETTY_H */
index 8ba0e31915021e3d60b814332c6a10c10f3a147e..110bcd741a9414bd90262b8e1ae9933e3198ca1c 100644 (file)
@@ -127,8 +127,8 @@ static struct used_atom {
                        unsigned int nobracket : 1, push : 1, push_remote : 1;
                } remote_ref;
                struct {
-                       enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH,
-                              C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
+                       enum { C_BARE, C_BODY, C_BODY_DEP, C_LENGTH, C_LINES,
+                              C_SIG, C_SUB, C_SUB_SANITIZE, C_TRAILERS } option;
                        struct process_trailer_options trailer_opts;
                        unsigned int nlines;
                } contents;
@@ -139,7 +139,10 @@ static struct used_atom {
                struct {
                        enum { O_FULL, O_LENGTH, O_SHORT } option;
                        unsigned int length;
-               } objectname;
+               } oid;
+               struct email_option {
+                       enum { EO_RAW, EO_TRIM, EO_LOCALPART } option;
+               } email_option;
                struct refname_atom refname;
                char *head;
        } u;
@@ -298,9 +301,12 @@ static int body_atom_parser(const struct ref_format *format, struct used_atom *a
 static int subject_atom_parser(const struct ref_format *format, struct used_atom *atom,
                               const char *arg, struct strbuf *err)
 {
-       if (arg)
-               return strbuf_addf_ret(err, -1, _("%%(subject) does not take arguments"));
-       atom->u.contents.option = C_SUB;
+       if (!arg)
+               atom->u.contents.option = C_SUB;
+       else if (!strcmp(arg, "sanitize"))
+               atom->u.contents.option = C_SUB_SANITIZE;
+       else
+               return strbuf_addf_ret(err, -1, _("unrecognized %%(subject) argument: %s"), arg);
        return 0;
 }
 
@@ -360,22 +366,36 @@ static int contents_atom_parser(const struct ref_format *format, struct used_ato
        return 0;
 }
 
-static int objectname_atom_parser(const struct ref_format *format, struct used_atom *atom,
-                                 const char *arg, struct strbuf *err)
+static int oid_atom_parser(const struct ref_format *format, struct used_atom *atom,
+                          const char *arg, struct strbuf *err)
 {
        if (!arg)
-               atom->u.objectname.option = O_FULL;
+               atom->u.oid.option = O_FULL;
        else if (!strcmp(arg, "short"))
-               atom->u.objectname.option = O_SHORT;
+               atom->u.oid.option = O_SHORT;
        else if (skip_prefix(arg, "short=", &arg)) {
-               atom->u.objectname.option = O_LENGTH;
-               if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
-                   atom->u.objectname.length == 0)
-                       return strbuf_addf_ret(err, -1, _("positive value expected objectname:short=%s"), arg);
-               if (atom->u.objectname.length < MINIMUM_ABBREV)
-                       atom->u.objectname.length = MINIMUM_ABBREV;
+               atom->u.oid.option = O_LENGTH;
+               if (strtoul_ui(arg, 10, &atom->u.oid.length) ||
+                   atom->u.oid.length == 0)
+                       return strbuf_addf_ret(err, -1, _("positive value expected '%s' in %%(%s)"), arg, atom->name);
+               if (atom->u.oid.length < MINIMUM_ABBREV)
+                       atom->u.oid.length = MINIMUM_ABBREV;
        } else
-               return strbuf_addf_ret(err, -1, _("unrecognized %%(objectname) argument: %s"), arg);
+               return strbuf_addf_ret(err, -1, _("unrecognized argument '%s' in %%(%s)"), arg, atom->name);
+       return 0;
+}
+
+static int person_email_atom_parser(const struct ref_format *format, struct used_atom *atom,
+                                   const char *arg, struct strbuf *err)
+{
+       if (!arg)
+               atom->u.email_option.option = EO_RAW;
+       else if (!strcmp(arg, "trim"))
+               atom->u.email_option.option = EO_TRIM;
+       else if (!strcmp(arg, "localpart"))
+               atom->u.email_option.option = EO_LOCALPART;
+       else
+               return strbuf_addf_ret(err, -1, _("unrecognized email option: %s"), arg);
        return 0;
 }
 
@@ -480,25 +500,25 @@ static struct {
        { "refname", SOURCE_NONE, FIELD_STR, refname_atom_parser },
        { "objecttype", SOURCE_OTHER, FIELD_STR, objecttype_atom_parser },
        { "objectsize", SOURCE_OTHER, FIELD_ULONG, objectsize_atom_parser },
-       { "objectname", SOURCE_OTHER, FIELD_STR, objectname_atom_parser },
+       { "objectname", SOURCE_OTHER, FIELD_STR, oid_atom_parser },
        { "deltabase", SOURCE_OTHER, FIELD_STR, deltabase_atom_parser },
-       { "tree", SOURCE_OBJ },
-       { "parent", SOURCE_OBJ },
+       { "tree", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
+       { "parent", SOURCE_OBJ, FIELD_STR, oid_atom_parser },
        { "numparent", SOURCE_OBJ, FIELD_ULONG },
        { "object", SOURCE_OBJ },
        { "type", SOURCE_OBJ },
        { "tag", SOURCE_OBJ },
        { "author", SOURCE_OBJ },
        { "authorname", SOURCE_OBJ },
-       { "authoremail", SOURCE_OBJ },
+       { "authoremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        { "authordate", SOURCE_OBJ, FIELD_TIME },
        { "committer", SOURCE_OBJ },
        { "committername", SOURCE_OBJ },
-       { "committeremail", SOURCE_OBJ },
+       { "committeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        { "committerdate", SOURCE_OBJ, FIELD_TIME },
        { "tagger", SOURCE_OBJ },
        { "taggername", SOURCE_OBJ },
-       { "taggeremail", SOURCE_OBJ },
+       { "taggeremail", SOURCE_OBJ, FIELD_STR, person_email_atom_parser },
        { "taggerdate", SOURCE_OBJ, FIELD_TIME },
        { "creator", SOURCE_OBJ },
        { "creatordate", SOURCE_OBJ, FIELD_TIME },
@@ -903,21 +923,27 @@ int verify_ref_format(struct ref_format *format)
        return 0;
 }
 
-static int grab_objectname(const char *name, const struct object_id *oid,
-                          struct atom_value *v, struct used_atom *atom)
+static const char *do_grab_oid(const char *field, const struct object_id *oid,
+                              struct used_atom *atom)
 {
-       if (starts_with(name, "objectname")) {
-               if (atom->u.objectname.option == O_SHORT) {
-                       v->s = xstrdup(find_unique_abbrev(oid, DEFAULT_ABBREV));
-                       return 1;
-               } else if (atom->u.objectname.option == O_FULL) {
-                       v->s = xstrdup(oid_to_hex(oid));
-                       return 1;
-               } else if (atom->u.objectname.option == O_LENGTH) {
-                       v->s = xstrdup(find_unique_abbrev(oid, atom->u.objectname.length));
-                       return 1;
-               } else
-                       BUG("unknown %%(objectname) option");
+       switch (atom->u.oid.option) {
+       case O_FULL:
+               return oid_to_hex(oid);
+       case O_LENGTH:
+               return find_unique_abbrev(oid, atom->u.oid.length);
+       case O_SHORT:
+               return find_unique_abbrev(oid, DEFAULT_ABBREV);
+       default:
+               BUG("unknown %%(%s) option", field);
+       }
+}
+
+static int grab_oid(const char *name, const char *field, const struct object_id *oid,
+                   struct atom_value *v, struct used_atom *atom)
+{
+       if (starts_with(name, field)) {
+               v->s = xstrdup(do_grab_oid(field, oid, atom));
+               return 1;
        }
        return 0;
 }
@@ -945,7 +971,7 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_
                } else if (!strcmp(name, "deltabase"))
                        v->s = xstrdup(oid_to_hex(&oi->delta_base_oid));
                else if (deref)
-                       grab_objectname(name, &oi->oid, v, &used_atom[i]);
+                       grab_oid(name, "objectname", &oi->oid, v, &used_atom[i]);
        }
 }
 
@@ -984,21 +1010,20 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object
                        continue;
                if (deref)
                        name++;
-               if (!strcmp(name, "tree")) {
-                       v->s = xstrdup(oid_to_hex(get_commit_tree_oid(commit)));
-               }
-               else if (!strcmp(name, "numparent")) {
+               if (grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i]))
+                       continue;
+               if (!strcmp(name, "numparent")) {
                        v->value = commit_list_count(commit->parents);
                        v->s = xstrfmt("%lu", (unsigned long)v->value);
                }
-               else if (!strcmp(name, "parent")) {
+               else if (starts_with(name, "parent")) {
                        struct commit_list *parents;
                        struct strbuf s = STRBUF_INIT;
                        for (parents = commit->parents; parents; parents = parents->next) {
-                               struct commit *parent = parents->item;
+                               struct object_id *oid = &parents->item->object.oid;
                                if (parents != commit->parents)
                                        strbuf_addch(&s, ' ');
-                               strbuf_addstr(&s, oid_to_hex(&parent->object.oid));
+                               strbuf_addstr(&s, do_grab_oid("parent", oid, &used_atom[i]));
                        }
                        v->s = strbuf_detach(&s, NULL);
                }
@@ -1039,16 +1064,35 @@ static const char *copy_name(const char *buf)
        return xstrdup("");
 }
 
-static const char *copy_email(const char *buf)
+static const char *copy_email(const char *buf, struct used_atom *atom)
 {
        const char *email = strchr(buf, '<');
        const char *eoemail;
        if (!email)
                return xstrdup("");
-       eoemail = strchr(email, '>');
+       switch (atom->u.email_option.option) {
+       case EO_RAW:
+               eoemail = strchr(email, '>');
+               if (eoemail)
+                       eoemail++;
+               break;
+       case EO_TRIM:
+               email++;
+               eoemail = strchr(email, '>');
+               break;
+       case EO_LOCALPART:
+               email++;
+               eoemail = strchr(email, '@');
+               if (!eoemail)
+                       eoemail = strchr(email, '>');
+               break;
+       default:
+               BUG("unknown email option");
+       }
+
        if (!eoemail)
                return xstrdup("");
-       return xmemdupz(email, eoemail + 1 - email);
+       return xmemdupz(email, eoemail - email);
 }
 
 static char *copy_subject(const char *buf, unsigned long len)
@@ -1118,7 +1162,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
                        continue;
                if (name[wholen] != 0 &&
                    strcmp(name + wholen, "name") &&
-                   strcmp(name + wholen, "email") &&
+                   !starts_with(name + wholen, "email") &&
                    !starts_with(name + wholen, "date"))
                        continue;
                if (!wholine)
@@ -1129,8 +1173,8 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void
                        v->s = copy_line(wholine);
                else if (!strcmp(name + wholen, "name"))
                        v->s = copy_name(wholine);
-               else if (!strcmp(name + wholen, "email"))
-                       v->s = copy_email(wholine);
+               else if (starts_with(name + wholen, "email"))
+                       v->s = copy_email(wholine, &used_atom[i]);
                else if (starts_with(name + wholen, "date"))
                        grab_date(wholine, v, name);
        }
@@ -1243,8 +1287,8 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
                        continue;
                if (deref)
                        name++;
-               if (strcmp(name, "subject") &&
-                   strcmp(name, "body") &&
+               if (strcmp(name, "body") &&
+                   !starts_with(name, "subject") &&
                    !starts_with(name, "trailers") &&
                    !starts_with(name, "contents"))
                        continue;
@@ -1256,7 +1300,11 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf)
 
                if (atom->u.contents.option == C_SUB)
                        v->s = copy_subject(subpos, sublen);
-               else if (atom->u.contents.option == C_BODY_DEP)
+               else if (atom->u.contents.option == C_SUB_SANITIZE) {
+                       struct strbuf sb = STRBUF_INIT;
+                       format_sanitized_subject(&sb, subpos, sublen);
+                       v->s = strbuf_detach(&sb, NULL);
+               } else if (atom->u.contents.option == C_BODY_DEP)
                        v->s = xmemdupz(bodypos, bodylen);
                else if (atom->u.contents.option == C_LENGTH)
                        v->s = xstrfmt("%"PRIuMAX, (uintmax_t)strlen(subpos));
@@ -1706,7 +1754,7 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
                                v->s = xstrdup(buf + 1);
                        }
                        continue;
-               } else if (!deref && grab_objectname(name, &ref->objectname, v, atom)) {
+               } else if (!deref && grab_oid(name, "objectname", &ref->objectname, v, atom)) {
                        continue;
                } else if (!strcmp(name, "HEAD")) {
                        if (atom->u.head && !strcmp(ref->refname, atom->u.head))
diff --git a/refs.c b/refs.c
index 156fdcd459f22b2f9b7c54c5d85afb086c54fe57..8374dfefa617a4a6c82acc4b2418ccae479c2b06 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -598,10 +598,14 @@ const char *git_default_branch_name(void)
  * to name a branch.
  */
 static char *substitute_branch_name(struct repository *r,
-                                   const char **string, int *len)
+                                   const char **string, int *len,
+                                   int nonfatal_dangling_mark)
 {
        struct strbuf buf = STRBUF_INIT;
-       int ret = repo_interpret_branch_name(r, *string, *len, &buf, 0);
+       struct interpret_branch_name_options options = {
+               .nonfatal_dangling_mark = nonfatal_dangling_mark
+       };
+       int ret = repo_interpret_branch_name(r, *string, *len, &buf, &options);
 
        if (ret == *len) {
                size_t size;
@@ -614,19 +618,15 @@ static char *substitute_branch_name(struct repository *r,
 }
 
 int repo_dwim_ref(struct repository *r, const char *str, int len,
-                 struct object_id *oid, char **ref)
+                 struct object_id *oid, char **ref, int nonfatal_dangling_mark)
 {
-       char *last_branch = substitute_branch_name(r, &str, &len);
+       char *last_branch = substitute_branch_name(r, &str, &len,
+                                                  nonfatal_dangling_mark);
        int   refs_found  = expand_ref(r, str, len, oid, ref);
        free(last_branch);
        return refs_found;
 }
 
-int dwim_ref(const char *str, int len, struct object_id *oid, char **ref)
-{
-       return repo_dwim_ref(the_repository, str, len, oid, ref);
-}
-
 int expand_ref(struct repository *repo, const char *str, int len,
               struct object_id *oid, char **ref)
 {
@@ -665,7 +665,7 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
                  struct object_id *oid, char **log)
 {
        struct ref_store *refs = get_main_ref_store(r);
-       char *last_branch = substitute_branch_name(r, &str, &len);
+       char *last_branch = substitute_branch_name(r, &str, &len, 0);
        const char **p;
        int logs_found = 0;
        struct strbuf path = STRBUF_INIT;
diff --git a/refs.h b/refs.h
index 04bd25019f344b72a3e05470f675dab9b4ffc19d..66955181569b3d71363d8da752245f2a1efb7b1f 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,8 @@
 #ifndef REFS_H
 #define REFS_H
 
+#include "cache.h"
+
 struct object_id;
 struct ref_store;
 struct repository;
@@ -151,9 +153,15 @@ struct strvec;
 void expand_ref_prefix(struct strvec *prefixes, const char *prefix);
 
 int expand_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
-int repo_dwim_ref(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
+int repo_dwim_ref(struct repository *r, const char *str, int len,
+                 struct object_id *oid, char **ref, int nonfatal_dangling_mark);
 int repo_dwim_log(struct repository *r, const char *str, int len, struct object_id *oid, char **ref);
-int dwim_ref(const char *str, int len, struct object_id *oid, char **ref);
+static inline int dwim_ref(const char *str, int len, struct object_id *oid,
+                          char **ref, int nonfatal_dangling_mark)
+{
+       return repo_dwim_ref(the_repository, str, len, oid, ref,
+                            nonfatal_dangling_mark);
+}
 int dwim_log(const char *str, int len, struct object_id *oid, char **ref);
 
 /*
index 5c042753420b4b2cd553c566122fbef366149cd2..eafc14cbe759ed98096e68ce196013a3b026c67a 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -1554,7 +1554,7 @@ static void set_merge(struct branch *ret)
                    strcmp(ret->remote_name, "."))
                        continue;
                if (dwim_ref(ret->merge_name[i], strlen(ret->merge_name[i]),
-                            &oid, &ref) == 1)
+                            &oid, &ref, 0) == 1)
                        ret->merge[i]->dst = ref;
                else
                        ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
index 08c2ad23af668a42cca30a5cebfcc2feee1ce895..1239023f93c61517a8d0ff7a9ef8a5124c1bc25a 100644 (file)
@@ -315,13 +315,14 @@ static void add_pending_object_with_path(struct rev_info *revs,
                                         const char *name, unsigned mode,
                                         const char *path)
 {
+       struct interpret_branch_name_options options = { 0 };
        if (!obj)
                return;
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
-               int len = interpret_branch_name(name, 0, &buf, 0);
+               int len = interpret_branch_name(name, 0, &buf, &options);
 
                if (0 < len && name[len] && buf.len)
                        strbuf_addstr(&buf, name + len);
@@ -2352,7 +2353,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
                revs->diffopt.flags.recursive = 1;
                revs->diffopt.flags.tree_in_recursive = 1;
        } else if (!strcmp(arg, "-m")) {
+               /*
+                * To "diff-index", "-m" means "match missing", and to the "log"
+                * family of commands, it means "show full diff for merges". Set
+                * both fields appropriately.
+                */
                revs->ignore_merges = 0;
+               revs->match_missing = 1;
        } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
                if (!strcmp(optarg, "off")) {
                        revs->ignore_merges = 1;
index c1e5bcf139d7a31d9bf851ada3bc8373f5ab932e..f6bf860d19e5a2997193c25873a5ba82e030f68b 100644 (file)
@@ -188,6 +188,7 @@ struct rev_info {
        unsigned int    diff:1,
                        full_diff:1,
                        show_root_diff:1,
+                       match_missing:1,
                        no_commit_id:1,
                        verbose_header:1,
                        combine_merges:1,
index 0b8cb5247abc05bcc498908fcd34b1e24c817976..0b23b86ceb4433ece828e780be180579a77f3fe8 100644 (file)
@@ -809,7 +809,7 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
 
        if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) {
                if (warn_ambiguous_refs && warn_on_object_refname_ambiguity) {
-                       refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref);
+                       refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
                        if (refs_found > 0) {
                                warning(warn_msg, len, str);
                                if (advice_object_name_warning)
@@ -860,11 +860,11 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
 
        if (!len && reflog_len)
                /* allow "@{...}" to mean the current branch reflog */
-               refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref);
+               refs_found = repo_dwim_ref(r, "HEAD", 4, oid, &real_ref, 0);
        else if (reflog_len)
                refs_found = repo_dwim_log(r, str, len, oid, &real_ref);
        else
-               refs_found = repo_dwim_ref(r, str, len, oid, &real_ref);
+               refs_found = repo_dwim_ref(r, str, len, oid, &real_ref, 0);
 
        if (!refs_found)
                return -1;
@@ -1427,9 +1427,12 @@ static int reinterpret(struct repository *r,
        struct strbuf tmp = STRBUF_INIT;
        int used = buf->len;
        int ret;
+       struct interpret_branch_name_options options = {
+               .allowed = allowed
+       };
 
        strbuf_add(buf, name + len, namelen - len);
-       ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, allowed);
+       ret = repo_interpret_branch_name(r, buf->buf, buf->len, &tmp, &options);
        /* that data was not interpreted, remove our cruft */
        if (ret < 0) {
                strbuf_setlen(buf, used);
@@ -1471,7 +1474,7 @@ static int interpret_branch_mark(struct repository *r,
                                 int (*get_mark)(const char *, int),
                                 const char *(*get_data)(struct branch *,
                                                         struct strbuf *),
-                                unsigned allowed)
+                                const struct interpret_branch_name_options *options)
 {
        int len;
        struct branch *branch;
@@ -1493,10 +1496,16 @@ static int interpret_branch_mark(struct repository *r,
                branch = branch_get(NULL);
 
        value = get_data(branch, &err);
-       if (!value)
-               die("%s", err.buf);
+       if (!value) {
+               if (options->nonfatal_dangling_mark) {
+                       strbuf_release(&err);
+                       return -1;
+               } else {
+                       die("%s", err.buf);
+               }
+       }
 
-       if (!branch_interpret_allowed(value, allowed))
+       if (!branch_interpret_allowed(value, options->allowed))
                return -1;
 
        set_shortened_ref(r, buf, value);
@@ -1506,7 +1515,7 @@ static int interpret_branch_mark(struct repository *r,
 int repo_interpret_branch_name(struct repository *r,
                               const char *name, int namelen,
                               struct strbuf *buf,
-                              unsigned allowed)
+                              const struct interpret_branch_name_options *options)
 {
        char *at;
        const char *start;
@@ -1515,7 +1524,7 @@ int repo_interpret_branch_name(struct repository *r,
        if (!namelen)
                namelen = strlen(name);
 
-       if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) {
+       if (!options->allowed || (options->allowed & INTERPRET_BRANCH_LOCAL)) {
                len = interpret_nth_prior_checkout(r, name, namelen, buf);
                if (!len) {
                        return len; /* syntax Ok, not enough switches */
@@ -1523,7 +1532,8 @@ int repo_interpret_branch_name(struct repository *r,
                        if (len == namelen)
                                return len; /* consumed all */
                        else
-                               return reinterpret(r, name, namelen, len, buf, allowed);
+                               return reinterpret(r, name, namelen, len, buf,
+                                                  options->allowed);
                }
        }
 
@@ -1531,22 +1541,22 @@ int repo_interpret_branch_name(struct repository *r,
             (at = memchr(start, '@', namelen - (start - name)));
             start = at + 1) {
 
-               if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) {
+               if (!options->allowed || (options->allowed & INTERPRET_BRANCH_HEAD)) {
                        len = interpret_empty_at(name, namelen, at - name, buf);
                        if (len > 0)
                                return reinterpret(r, name, namelen, len, buf,
-                                                  allowed);
+                                                  options->allowed);
                }
 
                len = interpret_branch_mark(r, name, namelen, at - name, buf,
                                            upstream_mark, branch_get_upstream,
-                                           allowed);
+                                           options);
                if (len > 0)
                        return len;
 
                len = interpret_branch_mark(r, name, namelen, at - name, buf,
                                            push_mark, branch_get_push,
-                                           allowed);
+                                           options);
                if (len > 0)
                        return len;
        }
@@ -1557,7 +1567,10 @@ int repo_interpret_branch_name(struct repository *r,
 void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)
 {
        int len = strlen(name);
-       int used = interpret_branch_name(name, len, sb, allowed);
+       struct interpret_branch_name_options options = {
+               .allowed = allowed
+       };
+       int used = interpret_branch_name(name, len, sb, &options);
 
        if (used < 0)
                used = 0;
index 3cbcf40dfc50583cb05d9ca9c50c01a16a5c23c8..6f8002fc9e2e33d650a5a3792b1faf48b4a969bf 100644 (file)
@@ -438,7 +438,7 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt,
         */
 }
 
-static int prepare_submodule_summary(struct rev_info *rev, const char *path,
+static int prepare_submodule_diff_summary(struct rev_info *rev, const char *path,
                struct commit *left, struct commit *right,
                struct commit_list *merge_bases)
 {
@@ -459,7 +459,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path,
        return prepare_revision_walk(rev);
 }
 
-static void print_submodule_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
+static void print_submodule_diff_summary(struct repository *r, struct rev_info *rev, struct diff_options *o)
 {
        static const char format[] = "  %m %s";
        struct strbuf sb = STRBUF_INIT;
@@ -610,7 +610,7 @@ output_header:
        strbuf_release(&sb);
 }
 
-void show_submodule_summary(struct diff_options *o, const char *path,
+void show_submodule_diff_summary(struct diff_options *o, const char *path,
                struct object_id *one, struct object_id *two,
                unsigned dirty_submodule)
 {
@@ -632,12 +632,12 @@ void show_submodule_summary(struct diff_options *o, const char *path,
                goto out;
 
        /* Treat revision walker failure the same as missing commits */
-       if (prepare_submodule_summary(&rev, path, left, right, merge_bases)) {
+       if (prepare_submodule_diff_summary(&rev, path, left, right, merge_bases)) {
                diff_emit_submodule_error(o, "(revision walker failed)\n");
                goto out;
        }
 
-       print_submodule_summary(sub, &rev, o);
+       print_submodule_diff_summary(sub, &rev, o);
 
 out:
        if (merge_bases)
index 9ce85c03fed9950d366edb4bf550db22ffced06b..4ac6e31cf1f7dd672995a7ec642720cd3c5fc0b8 100644 (file)
@@ -69,7 +69,7 @@ int parse_submodule_update_strategy(const char *value,
                                    struct submodule_update_strategy *dst);
 const char *submodule_strategy_to_string(const struct submodule_update_strategy *s);
 void handle_ignore_submodules_arg(struct diff_options *, const char *);
-void show_submodule_summary(struct diff_options *o, const char *path,
+void show_submodule_diff_summary(struct diff_options *o, const char *path,
                            struct object_id *one, struct object_id *two,
                            unsigned dirty_submodule);
 void show_submodule_inline_diff(struct diff_options *o, const char *path,
index 50222a10c5b1c68d5509c7e4952bdde79bc1a86c..2f7c3dcd0f85d41409a0a40520d2af821b2c147d 100755 (executable)
@@ -329,6 +329,15 @@ test_expect_success 'implicit bare & --separate-git-dir incompatible' '
        test_i18ngrep "incompatible" err
 '
 
+test_expect_success 'bare & --separate-git-dir incompatible within worktree' '
+       test_when_finished "rm -rf bare.git linkwt seprepo" &&
+       test_commit gumby &&
+       git clone --bare . bare.git &&
+       git -C bare.git worktree add --detach ../linkwt &&
+       test_must_fail git -C linkwt init --separate-git-dir seprepo 2>err &&
+       test_i18ngrep "incompatible" err
+'
+
 test_lazy_prereq GETCWD_IGNORES_PERMS '
        base=GETCWD_TEST_BASE_DIR &&
        mkdir -p $base/dir &&
@@ -405,6 +414,25 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
        test_path_is_dir realgitdir/refs
 '
 
+sep_git_dir_worktree ()  {
+       test_when_finished "rm -rf mainwt linkwt seprepo" &&
+       git init mainwt &&
+       test_commit -C mainwt gumby &&
+       git -C mainwt worktree add --detach ../linkwt &&
+       git -C "$1" init --separate-git-dir ../seprepo &&
+       git -C mainwt rev-parse --git-common-dir >expect &&
+       git -C linkwt rev-parse --git-common-dir >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 're-init to move gitdir with linked worktrees' '
+       sep_git_dir_worktree mainwt
+'
+
+test_expect_success 're-init to move gitdir within linked worktree' '
+       sep_git_dir_worktree linkwt
+'
+
 test_expect_success MINGW '.git hidden' '
        rm -rf newdir &&
        (
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
new file mode 100755 (executable)
index 0000000..1fe468b
--- /dev/null
@@ -0,0 +1,179 @@
+#!/bin/sh
+
+test_description='test git worktree repair'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit init
+'
+
+test_expect_success 'skip missing worktree' '
+       test_when_finished "git worktree prune" &&
+       git worktree add --detach missing &&
+       rm -rf missing &&
+       git worktree repair >out 2>err &&
+       test_must_be_empty out &&
+       test_must_be_empty err
+'
+
+test_expect_success 'worktree path not directory' '
+       test_when_finished "git worktree prune" &&
+       git worktree add --detach notdir &&
+       rm -rf notdir &&
+       >notdir &&
+       test_must_fail git worktree repair >out 2>err &&
+       test_must_be_empty out &&
+       test_i18ngrep "not a directory" err
+'
+
+test_expect_success "don't clobber .git repo" '
+       test_when_finished "rm -rf repo && git worktree prune" &&
+       git worktree add --detach repo &&
+       rm -rf repo &&
+       test_create_repo repo &&
+       test_must_fail git worktree repair >out 2>err &&
+       test_must_be_empty out &&
+       test_i18ngrep ".git is not a file" err
+'
+
+test_corrupt_gitfile () {
+       butcher=$1 &&
+       problem=$2 &&
+       repairdir=${3:-.} &&
+       test_when_finished 'rm -rf corrupt && git worktree prune' &&
+       git worktree add --detach corrupt &&
+       git -C corrupt rev-parse --absolute-git-dir >expect &&
+       eval "$butcher" &&
+       git -C "$repairdir" worktree repair >out 2>err &&
+       test_i18ngrep "$problem" out &&
+       test_must_be_empty err &&
+       git -C corrupt rev-parse --absolute-git-dir >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'repair missing .git file' '
+       test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken"
+'
+
+test_expect_success 'repair bogus .git file' '
+       test_corrupt_gitfile "echo \"gitdir: /nowhere\" >corrupt/.git" \
+               ".git file broken"
+'
+
+test_expect_success 'repair incorrect .git file' '
+       test_when_finished "rm -rf other && git worktree prune" &&
+       test_create_repo other &&
+       other=$(git -C other rev-parse --absolute-git-dir) &&
+       test_corrupt_gitfile "echo \"gitdir: $other\" >corrupt/.git" \
+               ".git file incorrect"
+'
+
+test_expect_success 'repair .git file from main/.git' '
+       test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" .git
+'
+
+test_expect_success 'repair .git file from linked worktree' '
+       test_when_finished "rm -rf other && git worktree prune" &&
+       git worktree add --detach other &&
+       test_corrupt_gitfile "rm -f corrupt/.git" ".git file broken" other
+'
+
+test_expect_success 'repair .git file from bare.git' '
+       test_when_finished "rm -rf bare.git corrupt && git worktree prune" &&
+       git clone --bare . bare.git &&
+       git -C bare.git worktree add --detach ../corrupt &&
+       git -C corrupt rev-parse --absolute-git-dir >expect &&
+       rm -f corrupt/.git &&
+       git -C bare.git worktree repair &&
+       git -C corrupt rev-parse --absolute-git-dir >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'invalid worktree path' '
+       test_must_fail git worktree repair /notvalid >out 2>err &&
+       test_must_be_empty out &&
+       test_i18ngrep "not a valid path" err
+'
+
+test_expect_success 'repo not found; .git not file' '
+       test_when_finished "rm -rf not-a-worktree" &&
+       test_create_repo not-a-worktree &&
+       test_must_fail git worktree repair not-a-worktree >out 2>err &&
+       test_must_be_empty out &&
+       test_i18ngrep ".git is not a file" err
+'
+
+test_expect_success 'repo not found; .git file broken' '
+       test_when_finished "rm -rf orig moved && git worktree prune" &&
+       git worktree add --detach orig &&
+       echo /invalid >orig/.git &&
+       mv orig moved &&
+       test_must_fail git worktree repair moved >out 2>err &&
+       test_must_be_empty out &&
+       test_i18ngrep ".git file broken" err
+'
+
+test_expect_success 'repair broken gitdir' '
+       test_when_finished "rm -rf orig moved && git worktree prune" &&
+       git worktree add --detach orig &&
+       sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+       rm .git/worktrees/orig/gitdir &&
+       mv orig moved &&
+       git worktree repair moved >out 2>err &&
+       test_cmp expect .git/worktrees/orig/gitdir &&
+       test_i18ngrep "gitdir unreadable" out &&
+       test_must_be_empty err
+'
+
+test_expect_success 'repair incorrect gitdir' '
+       test_when_finished "rm -rf orig moved && git worktree prune" &&
+       git worktree add --detach orig &&
+       sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+       mv orig moved &&
+       git worktree repair moved >out 2>err &&
+       test_cmp expect .git/worktrees/orig/gitdir &&
+       test_i18ngrep "gitdir incorrect" out &&
+       test_must_be_empty err
+'
+
+test_expect_success 'repair gitdir (implicit) from linked worktree' '
+       test_when_finished "rm -rf orig moved && git worktree prune" &&
+       git worktree add --detach orig &&
+       sed s,orig/\.git$,moved/.git, .git/worktrees/orig/gitdir >expect &&
+       mv orig moved &&
+       git -C moved worktree repair >out 2>err &&
+       test_cmp expect .git/worktrees/orig/gitdir &&
+       test_i18ngrep "gitdir incorrect" out &&
+       test_must_be_empty err
+'
+
+test_expect_success 'unable to repair gitdir (implicit) from main worktree' '
+       test_when_finished "rm -rf orig moved && git worktree prune" &&
+       git worktree add --detach orig &&
+       cat .git/worktrees/orig/gitdir >expect &&
+       mv orig moved &&
+       git worktree repair >out 2>err &&
+       test_cmp expect .git/worktrees/orig/gitdir &&
+       test_must_be_empty out &&
+       test_must_be_empty err
+'
+
+test_expect_success 'repair multiple gitdir files' '
+       test_when_finished "rm -rf orig1 orig2 moved1 moved2 &&
+               git worktree prune" &&
+       git worktree add --detach orig1 &&
+       git worktree add --detach orig2 &&
+       sed s,orig1/\.git$,moved1/.git, .git/worktrees/orig1/gitdir >expect1 &&
+       sed s,orig2/\.git$,moved2/.git, .git/worktrees/orig2/gitdir >expect2 &&
+       mv orig1 moved1 &&
+       mv orig2 moved2 &&
+       git worktree repair moved1 moved2 >out 2>err &&
+       test_cmp expect1 .git/worktrees/orig1/gitdir &&
+       test_cmp expect2 .git/worktrees/orig2/gitdir &&
+       test_i18ngrep "gitdir incorrect:.*orig1/gitdir$" out &&
+       test_i18ngrep "gitdir incorrect:.*orig2/gitdir$" out &&
+       test_must_be_empty err
+'
+
+test_done
index 43b1b5b2af6e978f5dc84543f5be1707941a2d38..f340b376bca55f0aa7af191f91527f8da076bf19 100755 (executable)
@@ -382,12 +382,52 @@ test_expect_success 'repack with the --no-progress option' '
        test_line_count = 0 err
 '
 
-test_expect_success 'repack removes multi-pack-index' '
+test_expect_success 'repack removes multi-pack-index when deleting packs' '
        test_path_is_file $objdir/pack/multi-pack-index &&
-       GIT_TEST_MULTI_PACK_INDEX=0 git repack -adf &&
+       # Set GIT_TEST_MULTI_PACK_INDEX to 0 to avoid writing a new
+       # multi-pack-index after repacking, but set "core.multiPackIndex" to
+       # true so that "git repack" can read the existing MIDX.
+       GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -adf &&
        test_path_is_missing $objdir/pack/multi-pack-index
 '
 
+test_expect_success 'repack preserves multi-pack-index when creating packs' '
+       git init preserve &&
+       test_when_finished "rm -fr preserve" &&
+       (
+               cd preserve &&
+               packdir=.git/objects/pack &&
+               midx=$packdir/multi-pack-index &&
+
+               test_commit 1 &&
+               pack1=$(git pack-objects --all $packdir/pack) &&
+               touch $packdir/pack-$pack1.keep &&
+               test_commit 2 &&
+               pack2=$(git pack-objects --revs $packdir/pack) &&
+               touch $packdir/pack-$pack2.keep &&
+
+               git multi-pack-index write &&
+               cp $midx $midx.bak &&
+
+               cat >pack-input <<-EOF &&
+               HEAD
+               ^HEAD~1
+               EOF
+               test_commit 3 &&
+               pack3=$(git pack-objects --revs $packdir/pack <pack-input) &&
+               test_commit 4 &&
+               pack4=$(git pack-objects --revs $packdir/pack <pack-input) &&
+
+               GIT_TEST_MULTI_PACK_INDEX=0 git -c core.multiPackIndex repack -ad &&
+               ls -la $packdir &&
+               test_path_is_file $packdir/pack-$pack1.pack &&
+               test_path_is_file $packdir/pack-$pack2.pack &&
+               test_path_is_missing $packdir/pack-$pack3.pack &&
+               test_path_is_missing $packdir/pack-$pack4.pack &&
+               test_cmp_bin $midx.bak $midx
+       )
+'
+
 compare_results_with_midx "after repack"
 
 test_expect_success 'multi-pack-index and pack-bitmap' '
index 58adee7d18ced9b18924cdd33e17bbc6368b9a9a..b359023189933e3dab334f22fce3d3ea35fd3701 100755 (executable)
@@ -116,7 +116,13 @@ test_atom head objectname:short $(git rev-parse --short refs/heads/master)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
 test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom head tree $(git rev-parse refs/heads/master^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/master^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/master^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/master^{tree})
 test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
 test_atom head numparent 0
 test_atom head object ''
 test_atom head type ''
@@ -125,19 +131,26 @@ test_atom head '*objecttype' ''
 test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
 test_atom head authorname 'A U Thor'
 test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
 test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
 test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head committername 'C O Mitter'
 test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
 test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head tag ''
 test_atom head tagger ''
 test_atom head taggername ''
 test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
 test_atom head taggerdate ''
 test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
 test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
 test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
 test_atom head contents:subject 'Initial'
 test_atom head body ''
 test_atom head contents:body ''
@@ -161,7 +174,13 @@ test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
 test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master)
 test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master)
 test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
 test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
 test_atom tag numparent ''
 test_atom tag object $(git rev-parse refs/tags/testtag^0)
 test_atom tag type 'commit'
@@ -170,19 +189,26 @@ test_atom tag '*objecttype' 'commit'
 test_atom tag author ''
 test_atom tag authorname ''
 test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
 test_atom tag authordate ''
 test_atom tag committer ''
 test_atom tag committername ''
 test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
 test_atom tag committerdate ''
 test_atom tag tag 'testtag'
 test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag taggername 'C O Mitter'
 test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
 test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
 test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
 test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
 test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
 test_atom tag contents:subject 'Tagging at 1151968727'
 test_atom tag body ''
 test_atom tag contents:body ''
@@ -564,10 +590,14 @@ test_atom refs/tags/taggerless tag 'taggerless'
 test_atom refs/tags/taggerless tagger ''
 test_atom refs/tags/taggerless taggername ''
 test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
 test_atom refs/tags/taggerless taggerdate ''
 test_atom refs/tags/taggerless committer ''
 test_atom refs/tags/taggerless committername ''
 test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
 test_atom refs/tags/taggerless committerdate ''
 test_atom refs/tags/taggerless subject 'Broken tag'
 
@@ -591,6 +621,7 @@ test_expect_success 'create tag with subject and body content' '
        git tag -F msg subject-body
 '
 test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
 test_atom refs/tags/subject-body body 'first body line
 second body line
 '
@@ -611,6 +642,7 @@ test_expect_success 'create tag with multiline subject' '
        git tag -F msg multiline
 '
 test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
 test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
 test_atom refs/tags/multiline body 'first body line
 second body line
@@ -643,6 +675,7 @@ sig='-----BEGIN PGP SIGNATURE-----
 
 PREREQ=GPG
 test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
 test_atom refs/tags/signed-empty contents:subject ''
 test_atom refs/tags/signed-empty body "$sig"
 test_atom refs/tags/signed-empty contents:body ''
@@ -650,6 +683,7 @@ test_atom refs/tags/signed-empty contents:signature "$sig"
 test_atom refs/tags/signed-empty contents "$sig"
 
 test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
 test_atom refs/tags/signed-short contents:subject 'subject line'
 test_atom refs/tags/signed-short body "$sig"
 test_atom refs/tags/signed-short contents:body ''
@@ -658,6 +692,7 @@ test_atom refs/tags/signed-short contents "subject line
 $sig"
 
 test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
 test_atom refs/tags/signed-long contents:subject 'subject line'
 test_atom refs/tags/signed-long body "body contents
 $sig"
index cc87d26619477dfc1c330ae3a0a6cf498e5bdad1..76088147089c9bacdf0cb9f7cb570c55c1ec3f4d 100755 (executable)
@@ -7,6 +7,12 @@ test_description='Summary support for submodules
 
 This test script tries to verify the sanity of summary subcommand of git submodule.
 '
+
+# NOTE: This test script uses 'git add' instead of 'git submodule add' to add
+# submodules to the superproject. Some submodule subcommands such as init and
+# deinit might not work as expected in this script. t7421 does not have this
+# caveat.
+#
 # NEEDSWORK: This test script is old fashioned and may need a big cleanup due to
 # various reasons, one of them being that there are lots of commands taking place
 # outside of 'test_expect_success' block, which is no longer in good-style.
diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh
new file mode 100755 (executable)
index 0000000..b070f13
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (C) 2020 Shourya Shukla
+#
+
+test_description='Summary support for submodules, adding them using git submodule add
+
+This test script tries to verify the sanity of summary subcommand of git submodule
+while making sure to add submodules using `git submodule add` instead of
+`git add` as done in t7401.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'summary test environment setup' '
+       git init sm &&
+       test_commit -C sm "add file" file file-content file-tag &&
+
+       git submodule add ./sm my-subm &&
+       test_tick &&
+       git commit -m "add submodule"
+'
+
+test_expect_success 'submodule summary output for initialized submodule' '
+       test_commit -C sm "add file2" file2 file2-content file2-tag &&
+       git submodule update --remote &&
+       test_tick &&
+       git commit -m "update submodule" my-subm &&
+       git submodule summary HEAD^ >actual &&
+       rev1=$(git -C sm rev-parse --short HEAD^) &&
+       rev2=$(git -C sm rev-parse --short HEAD) &&
+       cat >expected <<-EOF &&
+       * my-subm ${rev1}...${rev2} (1):
+         > add file2
+
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for deinitialized submodule' '
+       git submodule deinit my-subm &&
+       git submodule summary HEAD^ >actual &&
+       test_must_be_empty actual &&
+       git submodule update --init my-subm &&
+       git submodule summary HEAD^ >actual &&
+       rev1=$(git -C sm rev-parse --short HEAD^) &&
+       rev2=$(git -C sm rev-parse --short HEAD) &&
+       cat >expected <<-EOF &&
+       * my-subm ${rev1}...${rev2} (1):
+         > add file2
+
+       EOF
+       test_cmp expected actual
+'
+
+test_expect_success 'submodule summary output for submodules with changed paths' '
+       git mv my-subm subm &&
+       git commit -m "change submodule path" &&
+       rev=$(git -C sm rev-parse --short HEAD^) &&
+       git submodule summary HEAD^^ -- my-subm >actual 2>err &&
+       test_must_be_empty err &&
+       cat >expected <<-EOF &&
+       * my-subm ${rev}...0000000:
+
+       EOF
+       test_cmp expected actual
+'
+
+test_done
index e81759319f57b96c1b03afb51dcb93d620f13482..45e1f6ff683a0195a63c0a3a7f6d37cf9d97f623 100755 (executable)
@@ -846,6 +846,18 @@ test_expect_success 'status refreshes the index' '
        test_cmp expect output
 '
 
+test_expect_success 'status shows detached HEAD properly after checking out non-local upstream branch' '
+       test_when_finished rm -rf upstream downstream actual &&
+
+       test_create_repo upstream &&
+       test_commit -C upstream foo &&
+
+       git clone upstream downstream &&
+       git -C downstream checkout @{u} &&
+       git -C downstream status >actual &&
+       test_i18ngrep "HEAD detached at [0-9a-f]\\+" actual
+'
+
 test_expect_success 'setup status submodule summary' '
        test_create_repo sm && (
                cd sm &&
index 62217b4a6b26b4efad52643d25f21e7c98a2fa3e..46a5fb844766a74afee9223354801f26b7e3a908 100644 (file)
@@ -571,3 +571,138 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
        free_worktrees(worktrees);
        return ret;
 }
+
+/*
+ * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
+ * pointing at <repo>/worktrees/<id>.
+ */
+static void repair_gitfile(struct worktree *wt,
+                          worktree_repair_fn fn, void *cb_data)
+{
+       struct strbuf dotgit = STRBUF_INIT;
+       struct strbuf repo = STRBUF_INIT;
+       char *backlink;
+       const char *repair = NULL;
+       int err;
+
+       /* missing worktree can't be repaired */
+       if (!file_exists(wt->path))
+               return;
+
+       if (!is_directory(wt->path)) {
+               fn(1, wt->path, _("not a directory"), cb_data);
+               return;
+       }
+
+       strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
+       strbuf_addf(&dotgit, "%s/.git", wt->path);
+       backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
+
+       if (err == READ_GITFILE_ERR_NOT_A_FILE)
+               fn(1, wt->path, _(".git is not a file"), cb_data);
+       else if (err)
+               repair = _(".git file broken");
+       else if (fspathcmp(backlink, repo.buf))
+               repair = _(".git file incorrect");
+
+       if (repair) {
+               fn(0, wt->path, repair, cb_data);
+               write_file(dotgit.buf, "gitdir: %s", repo.buf);
+       }
+
+       free(backlink);
+       strbuf_release(&repo);
+       strbuf_release(&dotgit);
+}
+
+static void repair_noop(int iserr, const char *path, const char *msg,
+                       void *cb_data)
+{
+       /* nothing */
+}
+
+void repair_worktrees(worktree_repair_fn fn, void *cb_data)
+{
+       struct worktree **worktrees = get_worktrees();
+       struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
+
+       if (!fn)
+               fn = repair_noop;
+       for (; *wt; wt++)
+               repair_gitfile(*wt, fn, cb_data);
+       free_worktrees(worktrees);
+}
+
+static int is_main_worktree_path(const char *path)
+{
+       struct strbuf target = STRBUF_INIT;
+       struct strbuf maindir = STRBUF_INIT;
+       int cmp;
+
+       strbuf_add_real_path(&target, path);
+       strbuf_strip_suffix(&target, "/.git");
+       strbuf_add_real_path(&maindir, get_git_common_dir());
+       strbuf_strip_suffix(&maindir, "/.git");
+       cmp = fspathcmp(maindir.buf, target.buf);
+
+       strbuf_release(&maindir);
+       strbuf_release(&target);
+       return !cmp;
+}
+
+/*
+ * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
+ * the worktree's path.
+ */
+void repair_worktree_at_path(const char *path,
+                            worktree_repair_fn fn, void *cb_data)
+{
+       struct strbuf dotgit = STRBUF_INIT;
+       struct strbuf realdotgit = STRBUF_INIT;
+       struct strbuf gitdir = STRBUF_INIT;
+       struct strbuf olddotgit = STRBUF_INIT;
+       char *backlink = NULL;
+       const char *repair = NULL;
+       int err;
+
+       if (!fn)
+               fn = repair_noop;
+
+       if (is_main_worktree_path(path))
+               goto done;
+
+       strbuf_addf(&dotgit, "%s/.git", path);
+       if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
+               fn(1, path, _("not a valid path"), cb_data);
+               goto done;
+       }
+
+       backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+       if (err == READ_GITFILE_ERR_NOT_A_FILE) {
+               fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
+               goto done;
+       } else if (err) {
+               fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
+               goto done;
+       }
+
+       strbuf_addf(&gitdir, "%s/gitdir", backlink);
+       if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
+               repair = _("gitdir unreadable");
+       else {
+               strbuf_rtrim(&olddotgit);
+               if (fspathcmp(olddotgit.buf, realdotgit.buf))
+                       repair = _("gitdir incorrect");
+       }
+
+       if (repair) {
+               fn(0, gitdir.buf, repair, cb_data);
+               write_file(gitdir.buf, "%s", realdotgit.buf);
+       }
+done:
+       free(backlink);
+       strbuf_release(&olddotgit);
+       strbuf_release(&gitdir);
+       strbuf_release(&realdotgit);
+       strbuf_release(&dotgit);
+}
index 516744c433f1e627abe85a347d72e6516f98c171..ff7b62e43414d24e5c61c619352f47f034484aeb 100644 (file)
@@ -89,6 +89,29 @@ int validate_worktree(const struct worktree *wt,
 void update_worktree_location(struct worktree *wt,
                              const char *path_);
 
+typedef void (* worktree_repair_fn)(int iserr, const char *path,
+                                   const char *msg, void *cb_data);
+
+/*
+ * Visit each registered linked worktree and repair corruptions. For each
+ * repair made or error encountered while attempting a repair, the callback
+ * function, if non-NULL, is called with the path of the worktree and a
+ * description of the repair or error, along with the callback user-data.
+ */
+void repair_worktrees(worktree_repair_fn, void *cb_data);
+
+/*
+ * Repair administrative files corresponding to the worktree at the given path.
+ * The worktree's .git file pointing at the repository must be intact for the
+ * repair to succeed. Useful for re-associating an orphaned worktree with the
+ * repository if the worktree has been moved manually (without using "git
+ * worktree move"). For each repair made or error encountered while attempting
+ * a repair, the callback function, if non-NULL, is called with the path of the
+ * worktree and a description of the repair or error, along with the callback
+ * user-data.
+ */
+void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
+
 /*
  * Free up the memory for worktree(s)
  */
index bb0f9120de40f70578867dece592eeecdf0e1e23..d9872d543b5e367ddfa3cf37a97bc2e4207bd71c 100644 (file)
@@ -1569,7 +1569,7 @@ static void wt_status_get_detached_from(struct repository *r,
                return;
        }
 
-       if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
+       if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref, 1) == 1 &&
            /* sha1 is a commit? match without further lookup */
            (oideq(&cb.noid, &oid) ||
             /* perhaps sha1 is a tag, try to dereference to a commit */