]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'ta/doc-typofix'
authorJunio C Hamano <gitster@pobox.com>
Fri, 15 Jan 2021 23:20:28 +0000 (15:20 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 15 Jan 2021 23:20:28 +0000 (15:20 -0800)
Doc fix.

* ta/doc-typofix:
  doc: fix some typos

41 files changed:
Documentation/RelNotes/2.31.0.txt [new file with mode: 0644]
Documentation/config/core.txt
Documentation/git-rev-parse.txt
Documentation/git-worktree.txt
Documentation/pretty-formats.txt
GIT-VERSION-GEN
RelNotes
abspath.c
builtin/pack-objects.c
builtin/pull.c
builtin/rev-parse.c
builtin/worktree.c
cache.h
commit.c
commit.h
config.c
ewah/bitmap.c
ewah/ewah_bitmap.c
ewah/ewok.h
merge-ort.c
merge-ort.h
merge-recursive.c
pack-bitmap-write.c
pack-bitmap.c
pack-bitmap.h
pretty.c
submodule.c
t/perf/perf-lib.sh
t/t1500-rev-parse.sh
t/t2406-worktree-repair.sh
t/t3200-branch.sh
t/t4205-log-pretty-formats.sh
t/t5310-pack-bitmaps.sh
t/t5526-fetch-submodules.sh
t/t6030-bisect-porcelain.sh
t/t7601-merge-pull-config.sh
trailer.c
trailer.h
tree.c
tree.h
worktree.c

diff --git a/Documentation/RelNotes/2.31.0.txt b/Documentation/RelNotes/2.31.0.txt
new file mode 100644 (file)
index 0000000..6bde597
--- /dev/null
@@ -0,0 +1,32 @@
+Git 2.31 Release Notes
+======================
+
+Updates since v2.30
+-------------------
+
+UI, Workflows & Features
+
+ * The "--format=%(trailers)" mechanism gets enhanced to make it
+   easier to design output for machine consumption.
+
+ * When a user does not tell "git pull" to use rebase or merge, the
+   command gives a loud message telling a user to choose between
+   rebase or merge but creates a merge anyway, forcing users who would
+   want to rebase to redo the operation.  Fix an early part of this
+   problem by tightening the condition to give the message---there is
+   no reason to stop or force the user to choose between rebase or
+   merge if the history fast-forwards.
+
+
+Performance, Internal Implementation, Development Support etc.
+
+ * A 3-year old test that was not testing anything useful has been
+   corrected.
+
+
+Fixes since v2.30
+-----------------
+
+ * Other code cleanup, docfix, build fix, etc.
+   (merge 505a276596 pk/subsub-fetch-fix-take-2 later to maint).
+   (merge 33fc56253b fc/t6030-bisect-reset-removes-auxiliary-files later to maint).
index 160aacad84bae60ff69ea73b9edbb63bee9c2b56..c04f62a54a154cd6315f974844c52c950d715c3c 100644 (file)
@@ -625,4 +625,6 @@ core.abbrev::
        computed based on the approximate number of packed objects
        in your repository, which hopefully is enough for
        abbreviated object names to stay unique for some time.
+       If set to "no", no abbreviation is made and the object names
+       are shown in their full length.
        The minimum length is 4.
index 5013daa6efebdd92ea93138a751b5a1c61877a56..6b8ca085aa6da56fa561ede46395e2be5a6d26be 100644 (file)
@@ -212,6 +212,18 @@ Options for Files
        Only the names of the variables are listed, not their value,
        even if they are set.
 
+--path-format=(absolute|relative)::
+       Controls the behavior of certain other options. If specified as absolute, the
+       paths printed by those options will be absolute and canonical. If specified as
+       relative, the paths will be relative to the current working directory if that
+       is possible.  The default is option specific.
++
+This option may be specified multiple times and affects only the arguments that
+follow it on the command line, either to the end of the command line or the next
+instance of this option.
+
+The following options are modified by `--path-format`:
+
 --git-dir::
        Show `$GIT_DIR` if defined. Otherwise show the path to
        the .git directory. The path shown, when relative, is
@@ -221,27 +233,9 @@ If `$GIT_DIR` is not defined and the current directory
 is not detected to lie in a Git repository or work tree
 print a message to stderr and exit with nonzero status.
 
---absolute-git-dir::
-       Like `--git-dir`, but its output is always the canonicalized
-       absolute path.
-
 --git-common-dir::
        Show `$GIT_COMMON_DIR` if defined, else `$GIT_DIR`.
 
---is-inside-git-dir::
-       When the current working directory is below the repository
-       directory print "true", otherwise "false".
-
---is-inside-work-tree::
-       When the current working directory is inside the work tree of the
-       repository print "true", otherwise "false".
-
---is-bare-repository::
-       When the repository is bare print "true", otherwise "false".
-
---is-shallow-repository::
-       When the repository is shallow print "true", otherwise "false".
-
 --resolve-git-dir <path>::
        Check if <path> is a valid repository or a gitfile that
        points at a valid repository, and print the location of the
@@ -255,19 +249,9 @@ print a message to stderr and exit with nonzero status.
        $GIT_OBJECT_DIRECTORY is set to /foo/bar then "git rev-parse
        --git-path objects/abc" returns /foo/bar/abc.
 
---show-cdup::
-       When the command is invoked from a subdirectory, show the
-       path of the top-level directory relative to the current
-       directory (typically a sequence of "../", or an empty string).
-
---show-prefix::
-       When the command is invoked from a subdirectory, show the
-       path of the current directory relative to the top-level
-       directory.
-
 --show-toplevel::
-       Show the absolute path of the top-level directory of the working
-       tree. If there is no working tree, report an error.
+       Show the (by default, absolute) path of the top-level directory
+       of the working tree. If there is no working tree, report an error.
 
 --show-superproject-working-tree::
        Show the absolute path of the root of the superproject's
@@ -279,6 +263,36 @@ print a message to stderr and exit with nonzero status.
        Show the path to the shared index file in split index mode, or
        empty if not in split-index mode.
 
+The following options are unaffected by `--path-format`:
+
+--absolute-git-dir::
+       Like `--git-dir`, but its output is always the canonicalized
+       absolute path.
+
+--is-inside-git-dir::
+       When the current working directory is below the repository
+       directory print "true", otherwise "false".
+
+--is-inside-work-tree::
+       When the current working directory is inside the work tree of the
+       repository print "true", otherwise "false".
+
+--is-bare-repository::
+       When the repository is bare print "true", otherwise "false".
+
+--is-shallow-repository::
+       When the repository is shallow print "true", otherwise "false".
+
+--show-cdup::
+       When the command is invoked from a subdirectory, show the
+       path of the top-level directory relative to the current
+       directory (typically a sequence of "../", or an empty string).
+
+--show-prefix::
+       When the command is invoked from a subdirectory, show the
+       path of the current directory relative to the top-level
+       directory.
+
 --show-object-format[=(storage|input|output)]::
        Show the object format (hash algorithm) used for the repository
        for storage inside the `.git` directory, input, or output. For
index af06128cc9e656de17a3f2b9180ca14135f0418c..02a706c4c0eef7a4e1beed944f738f2676525682 100644 (file)
@@ -143,6 +143,11 @@ 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.
++
+If both the main working tree and linked working trees have been moved
+manually, then running `repair` in the main working tree and specifying the
+new `<path>` of each linked working tree will reestablish all connections
+in both directions.
 
 unlock::
 
index 84bbc7439a601b2e18a7ea48af9cfcdd62353e64..6b59e28d444f8e1f033f435ce0c396aca2516865 100644 (file)
@@ -252,7 +252,15 @@ endif::git-rev-list[]
                          interpreted by
                          linkgit:git-interpret-trailers[1]. The
                          `trailers` string may be followed by a colon
-                         and zero or more comma-separated options:
+                         and zero or more comma-separated options.
+                         If any option is provided multiple times the
+                         last occurance wins.
++
+The boolean options accept an optional value `[=<BOOL>]`. The values
+`true`, `false`, `on`, `off` etc. are all accepted. See the "boolean"
+sub-section in "EXAMPLES" in linkgit:git-config[1]. If a boolean
+option is given with no value, it's enabled.
++
 ** 'key=<K>': only show trailers with specified key. Matching is done
    case-insensitively and trailing colon is optional. If option is
    given multiple times trailer lines matching any of the keys are
@@ -261,27 +269,25 @@ endif::git-rev-list[]
    desired it can be disabled with `only=false`.  E.g.,
    `%(trailers:key=Reviewed-by)` shows trailer lines with key
    `Reviewed-by`.
-** 'only[=val]': select whether non-trailer lines from the trailer
-   block should be included. The `only` keyword may optionally be
-   followed by an equal sign and one of `true`, `on`, `yes` to omit or
-   `false`, `off`, `no` to show the non-trailer lines. If option is
-   given without value it is enabled. If given multiple times the last
-   value is used.
+** 'only[=<BOOL>]': select whether non-trailer lines from the trailer
+   block should be included.
 ** 'separator=<SEP>': specify a separator inserted between trailer
    lines. When this option is not given each trailer line is
    terminated with a line feed character. The string SEP may contain
    the literal formatting codes described above. To use comma as
    separator one must use `%x2C` as it would otherwise be parsed as
-   next option. If separator option is given multiple times only the
-   last one is used. E.g., `%(trailers:key=Ticket,separator=%x2C )`
+   next option. E.g., `%(trailers:key=Ticket,separator=%x2C )`
    shows all trailer lines whose key is "Ticket" separated by a comma
    and a space.
-** 'unfold[=val]': make it behave as if interpret-trailer's `--unfold`
-   option was given. In same way as to for `only` it can be followed
-   by an equal sign and explicit value. E.g.,
+** 'unfold[=<BOOL>]': make it behave as if interpret-trailer's `--unfold`
+   option was given. E.g.,
    `%(trailers:only,unfold=true)` unfolds and shows all trailer lines.
-** 'valueonly[=val]': skip over the key part of the trailer line and only
-   show the value part. Also this optionally allows explicit value.
+** 'keyonly[=<BOOL>]': only show the key part of the trailer.
+** 'valueonly[=<BOOL>]': only show the value part of the trailer.
+** 'key_value_separator=<SEP>': specify a separator inserted between
+   trailer lines. When this option is not given each trailer key-value
+   pair is separated by ": ". Otherwise it shares the same semantics
+   as 'separator=<SEP>' above.
 
 NOTE: Some placeholders may depend on other options given to the
 revision traversal engine. For example, the `%g*` reflog options will
index 5085ef1390ca5589f6e553aacb177e7336504de6..0327733794e4022af0e1d916f560daea7ed2d4f1 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.30.0
+DEF_VER=v2.30.GIT
 
 LF='
 '
index dc8c04b4f4aed68baeccc7a7bbafad37e52c8320..3324fc058d6c9c0a473955ff50eefbf2e3479123 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.30.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.31.0.txt
\ No newline at end of file
index 6f15a418bb64e202811a05e14819f6ef6a578b8d..39e06b58486e3e94e640929c27460e786533a2f8 100644 (file)
--- a/abspath.c
+++ b/abspath.c
@@ -67,19 +67,15 @@ static void get_root_part(struct strbuf *resolved, struct strbuf *remaining)
 #endif
 
 /*
- * Return the real path (i.e., absolute path, with symlinks resolved
- * and extra slashes removed) equivalent to the specified path.  (If
- * you want an absolute path but don't mind links, use
- * absolute_path().)  Places the resolved realpath in the provided strbuf.
- *
- * The directory part of path (i.e., everything up to the last
- * dir_sep) must denote a valid, existing directory, but the last
- * component need not exist.  If die_on_error is set, then die with an
- * informative error message if there is a problem.  Otherwise, return
- * NULL on errors (without generating any output).
+ * If set, any number of trailing components may be missing; otherwise, only one
+ * may be.
  */
-char *strbuf_realpath(struct strbuf *resolved, const char *path,
-                     int die_on_error)
+#define REALPATH_MANY_MISSING (1 << 0)
+/* Should we die if there's an error? */
+#define REALPATH_DIE_ON_ERROR (1 << 1)
+
+static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
+                              int flags)
 {
        struct strbuf remaining = STRBUF_INIT;
        struct strbuf next = STRBUF_INIT;
@@ -89,7 +85,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
        struct stat st;
 
        if (!*path) {
-               if (die_on_error)
+               if (flags & REALPATH_DIE_ON_ERROR)
                        die("The empty string is not a valid path");
                else
                        goto error_out;
@@ -101,7 +97,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
        if (!resolved->len) {
                /* relative path; can use CWD as the initial resolved path */
                if (strbuf_getcwd(resolved)) {
-                       if (die_on_error)
+                       if (flags & REALPATH_DIE_ON_ERROR)
                                die_errno("unable to get current working directory");
                        else
                                goto error_out;
@@ -129,8 +125,9 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
 
                if (lstat(resolved->buf, &st)) {
                        /* error out unless this was the last component */
-                       if (errno != ENOENT || remaining.len) {
-                               if (die_on_error)
+                       if (errno != ENOENT ||
+                          (!(flags & REALPATH_MANY_MISSING) && remaining.len)) {
+                               if (flags & REALPATH_DIE_ON_ERROR)
                                        die_errno("Invalid path '%s'",
                                                  resolved->buf);
                                else
@@ -143,7 +140,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
                        if (num_symlinks++ > MAXSYMLINKS) {
                                errno = ELOOP;
 
-                               if (die_on_error)
+                               if (flags & REALPATH_DIE_ON_ERROR)
                                        die("More than %d nested symlinks "
                                            "on path '%s'", MAXSYMLINKS, path);
                                else
@@ -153,7 +150,7 @@ char *strbuf_realpath(struct strbuf *resolved, const char *path,
                        len = strbuf_readlink(&symlink, resolved->buf,
                                              st.st_size);
                        if (len < 0) {
-                               if (die_on_error)
+                               if (flags & REALPATH_DIE_ON_ERROR)
                                        die_errno("Invalid symlink '%s'",
                                                  resolved->buf);
                                else
@@ -202,6 +199,37 @@ error_out:
        return retval;
 }
 
+/*
+ * Return the real path (i.e., absolute path, with symlinks resolved
+ * and extra slashes removed) equivalent to the specified path.  (If
+ * you want an absolute path but don't mind links, use
+ * absolute_path().)  Places the resolved realpath in the provided strbuf.
+ *
+ * The directory part of path (i.e., everything up to the last
+ * dir_sep) must denote a valid, existing directory, but the last
+ * component need not exist.  If die_on_error is set, then die with an
+ * informative error message if there is a problem.  Otherwise, return
+ * NULL on errors (without generating any output).
+ */
+char *strbuf_realpath(struct strbuf *resolved, const char *path,
+                     int die_on_error)
+{
+       return strbuf_realpath_1(resolved, path,
+                                die_on_error ? REALPATH_DIE_ON_ERROR : 0);
+}
+
+/*
+ * Just like strbuf_realpath, but allows an arbitrary number of path
+ * components to be missing.
+ */
+char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path,
+                               int die_on_error)
+{
+       return strbuf_realpath_1(resolved, path,
+                                ((die_on_error ? REALPATH_DIE_ON_ERROR : 0) |
+                                 REALPATH_MANY_MISSING));
+}
+
 char *real_pathdup(const char *path, int die_on_error)
 {
        struct strbuf realpath = STRBUF_INIT;
index 5617c01b5aae0dafd33467cecabe8b58fd36d568..2a00358f3452febd9c647cdb9029e5a04f89833d 100644 (file)
@@ -1104,7 +1104,6 @@ static void write_pack_file(void)
                                stop_progress(&progress_state);
 
                                bitmap_writer_show_progress(progress);
-                               bitmap_writer_reuse_bitmaps(&to_pack);
                                bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
                                bitmap_writer_build(&to_pack);
                                bitmap_writer_finish(written_list, nr_written,
index aa56ebcdd0dce08f61fcaa5d1762d4efb8876adc..e8927fc2ffe3398de10b2a695f5b6346ac5a540c 100644 (file)
@@ -324,7 +324,7 @@ static const char *config_get_ff(void)
  * looks for the value of "pull.rebase". If both configuration keys do not
  * exist, returns REBASE_FALSE.
  */
-static enum rebase_type config_get_rebase(void)
+static enum rebase_type config_get_rebase(int *rebase_unspecified)
 {
        struct branch *curr_branch = branch_get("HEAD");
        const char *value;
@@ -344,20 +344,7 @@ static enum rebase_type config_get_rebase(void)
        if (!git_config_get_value("pull.rebase", &value))
                return parse_config_rebase("pull.rebase", value, 1);
 
-       if (opt_verbosity >= 0 && !opt_ff) {
-               advise(_("Pulling without specifying how to reconcile divergent branches is\n"
-                        "discouraged. You can squelch this message by running one of the following\n"
-                        "commands sometime before your next pull:\n"
-                        "\n"
-                        "  git config pull.rebase false  # merge (the default strategy)\n"
-                        "  git config pull.rebase true   # rebase\n"
-                        "  git config pull.ff only       # fast-forward only\n"
-                        "\n"
-                        "You can replace \"git config\" with \"git config --global\" to set a default\n"
-                        "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
-                        "or --ff-only on the command line to override the configured default per\n"
-                        "invocation.\n"));
-       }
+       *rebase_unspecified = 1;
 
        return REBASE_FALSE;
 }
@@ -924,6 +911,36 @@ static int run_rebase(const struct object_id *newbase,
        return ret;
 }
 
+static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)
+{
+       int ret;
+       struct commit_list *list = NULL;
+       struct commit *merge_head, *head;
+
+       head = lookup_commit_reference(the_repository, orig_head);
+       commit_list_insert(head, &list);
+       merge_head = lookup_commit_reference(the_repository, orig_merge_head);
+       ret = repo_is_descendant_of(the_repository, merge_head, list);
+       free_commit_list(list);
+       return ret;
+}
+
+static void show_advice_pull_non_ff(void)
+{
+       advise(_("Pulling without specifying how to reconcile divergent branches is\n"
+                "discouraged. You can squelch this message by running one of the following\n"
+                "commands sometime before your next pull:\n"
+                "\n"
+                "  git config pull.rebase false  # merge (the default strategy)\n"
+                "  git config pull.rebase true   # rebase\n"
+                "  git config pull.ff only       # fast-forward only\n"
+                "\n"
+                "You can replace \"git config\" with \"git config --global\" to set a default\n"
+                "preference for all repositories. You can also pass --rebase, --no-rebase,\n"
+                "or --ff-only on the command line to override the configured default per\n"
+                "invocation.\n"));
+}
+
 int cmd_pull(int argc, const char **argv, const char *prefix)
 {
        const char *repo, **refspecs;
@@ -931,6 +948,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
        struct object_id orig_head, curr_head;
        struct object_id rebase_fork_point;
        int autostash;
+       int rebase_unspecified = 0;
+       int can_ff;
 
        if (!getenv("GIT_REFLOG_ACTION"))
                set_reflog_message(argc, argv);
@@ -952,7 +971,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
                opt_ff = xstrdup_or_null(config_get_ff());
 
        if (opt_rebase < 0)
-               opt_rebase = config_get_rebase();
+               opt_rebase = config_get_rebase(&rebase_unspecified);
 
        if (read_cache_unmerged())
                die_resolve_conflict("pull");
@@ -1026,6 +1045,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
        if (opt_rebase && merge_heads.nr > 1)
                die(_("Cannot rebase onto multiple branches."));
 
+       can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);
+
+       if (rebase_unspecified && !opt_ff && !can_ff) {
+               if (opt_verbosity >= 0)
+                       show_advice_pull_non_ff();
+       }
+
        if (opt_rebase) {
                int ret = 0;
                int ran_ff = 0;
@@ -1040,22 +1066,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
                    submodule_touches_in_range(the_repository, &upstream, &curr_head))
                        die(_("cannot rebase with locally recorded submodule modifications"));
                if (!autostash) {
-                       struct commit_list *list = NULL;
-                       struct commit *merge_head, *head;
-
-                       head = lookup_commit_reference(the_repository,
-                                                      &orig_head);
-                       commit_list_insert(head, &list);
-                       merge_head = lookup_commit_reference(the_repository,
-                                                            &merge_heads.oid[0]);
-                       if (repo_is_descendant_of(the_repository,
-                                                 merge_head, list)) {
+                       if (can_ff) {
                                /* we can fast-forward this without invoking rebase */
                                opt_ff = "--ff-only";
                                ran_ff = 1;
                                ret = run_merge();
                        }
-                       free_commit_list(list);
                }
                if (!ran_ff)
                        ret = run_rebase(&newbase, &upstream);
index 69ba7326cf823a774d3d673a283cfe08a0fce3fc..85bad9052ebcce8e84875cbfb0a5a4da4be7f3ac 100644 (file)
@@ -583,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix)
        clear_ref_exclusion(&ref_excludes);
 }
 
+enum format_type {
+       /* We would like a relative path. */
+       FORMAT_RELATIVE,
+       /* We would like a canonical absolute path. */
+       FORMAT_CANONICAL,
+       /* We would like the default behavior. */
+       FORMAT_DEFAULT,
+};
+
+enum default_type {
+       /* Our default is a relative path. */
+       DEFAULT_RELATIVE,
+       /* Our default is a relative path if there's a shared root. */
+       DEFAULT_RELATIVE_IF_SHARED,
+       /* Our default is a canonical absolute path. */
+       DEFAULT_CANONICAL,
+       /* Our default is not to modify the item. */
+       DEFAULT_UNMODIFIED,
+};
+
+static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def)
+{
+       char *cwd = NULL;
+       /*
+        * We don't ever produce a relative path if prefix is NULL, so set the
+        * prefix to the current directory so that we can produce a relative
+        * path whenever possible.  If we're using RELATIVE_IF_SHARED mode, then
+        * we want an absolute path unless the two share a common prefix, so don't
+        * set it in that case, since doing so causes a relative path to always
+        * be produced if possible.
+        */
+       if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED))
+               prefix = cwd = xgetcwd();
+       if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) {
+               puts(path);
+       } else if (format == FORMAT_RELATIVE ||
+                 (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) {
+               /*
+                * In order for relative_path to work as expected, we need to
+                * make sure that both paths are absolute paths.  If we don't,
+                * we can end up with an unexpected absolute path that the user
+                * didn't want.
+                */
+               struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT;
+               if (!is_absolute_path(path)) {
+                       strbuf_realpath_forgiving(&realbuf, path,  1);
+                       path = realbuf.buf;
+               }
+               if (!is_absolute_path(prefix)) {
+                       strbuf_realpath_forgiving(&prefixbuf, prefix, 1);
+                       prefix = prefixbuf.buf;
+               }
+               puts(relative_path(path, prefix, &buf));
+               strbuf_release(&buf);
+               strbuf_release(&realbuf);
+               strbuf_release(&prefixbuf);
+       } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) {
+               struct strbuf buf = STRBUF_INIT;
+               puts(relative_path(path, prefix, &buf));
+               strbuf_release(&buf);
+       } else {
+               struct strbuf buf = STRBUF_INIT;
+               strbuf_realpath_forgiving(&buf, path, 1);
+               puts(buf.buf);
+               strbuf_release(&buf);
+       }
+       free(cwd);
+}
+
 int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 {
        int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
@@ -596,6 +665,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
        struct strbuf buf = STRBUF_INIT;
        const int hexsz = the_hash_algo->hexsz;
        int seen_end_of_options = 0;
+       enum format_type format = FORMAT_DEFAULT;
 
        if (argc > 1 && !strcmp("--parseopt", argv[1]))
                return cmd_parseopt(argc - 1, argv + 1, prefix);
@@ -668,8 +738,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                if (!argv[i + 1])
                                        die("--git-path requires an argument");
                                strbuf_reset(&buf);
-                               puts(relative_path(git_path("%s", argv[i + 1]),
-                                                  prefix, &buf));
+                               print_path(git_path("%s", argv[i + 1]), prefix,
+                                               format,
+                                               DEFAULT_RELATIVE_IF_SHARED);
                                i++;
                                continue;
                        }
@@ -687,6 +758,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                        show(arg);
                                continue;
                        }
+                       if (opt_with_value(arg, "--path-format", &arg)) {
+                               if (!strcmp(arg, "absolute")) {
+                                       format = FORMAT_CANONICAL;
+                               } else if (!strcmp(arg, "relative")) {
+                                       format = FORMAT_RELATIVE;
+                               } else {
+                                       die("unknown argument to --path-format: %s", arg);
+                               }
+                               continue;
+                       }
                        if (!strcmp(arg, "--default")) {
                                def = argv[++i];
                                if (!def)
@@ -807,7 +888,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                        if (!strcmp(arg, "--show-toplevel")) {
                                const char *work_tree = get_git_work_tree();
                                if (work_tree)
-                                       puts(work_tree);
+                                       print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED);
                                else
                                        die("this operation must be run in a work tree");
                                continue;
@@ -815,7 +896,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                        if (!strcmp(arg, "--show-superproject-working-tree")) {
                                struct strbuf superproject = STRBUF_INIT;
                                if (get_superproject_working_tree(&superproject))
-                                       puts(superproject.buf);
+                                       print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED);
                                strbuf_release(&superproject);
                                continue;
                        }
@@ -850,16 +931,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
                                char *cwd;
                                int len;
+                               enum format_type wanted = format;
                                if (arg[2] == 'g') {    /* --git-dir */
                                        if (gitdir) {
-                                               puts(gitdir);
+                                               print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED);
                                                continue;
                                        }
                                        if (!prefix) {
-                                               puts(".git");
+                                               print_path(".git", prefix, format, DEFAULT_UNMODIFIED);
                                                continue;
                                        }
                                } else {                /* --absolute-git-dir */
+                                       wanted = FORMAT_CANONICAL;
                                        if (!gitdir && !prefix)
                                                gitdir = ".git";
                                        if (gitdir) {
@@ -872,14 +955,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                }
                                cwd = xgetcwd();
                                len = strlen(cwd);
-                               printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : "");
+                               strbuf_reset(&buf);
+                               strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : "");
                                free(cwd);
+                               print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL);
                                continue;
                        }
                        if (!strcmp(arg, "--git-common-dir")) {
-                               strbuf_reset(&buf);
-                               puts(relative_path(get_git_common_dir(),
-                                                  prefix, &buf));
+                               print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED);
                                continue;
                        }
                        if (!strcmp(arg, "--is-inside-git-dir")) {
@@ -909,8 +992,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                if (the_index.split_index) {
                                        const struct object_id *oid = &the_index.split_index->base_oid;
                                        const char *path = git_path("sharedindex.%s", oid_to_hex(oid));
-                                       strbuf_reset(&buf);
-                                       puts(relative_path(path, prefix, &buf));
+                                       print_path(path, prefix, format, DEFAULT_RELATIVE);
                                }
                                continue;
                        }
index 197fd24a555f010126b81813d5d98cc40a4cf1e2..71287b2da6ccf54afe575b443592468121fa4e5a 100644 (file)
@@ -1052,10 +1052,10 @@ static int repair(int ac, const char **av, const char *prefix)
        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);
+       repair_worktrees(report_repair, &rc);
        return rc;
 }
 
diff --git a/cache.h b/cache.h
index 71097657489362059c225ed084ec5141aefe9384..eefa93b08f8891ec4c1c4234ccf2254d7d118ec6 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1231,6 +1231,8 @@ static inline int is_absolute_path(const char *path)
 int is_directory(const char *);
 char *strbuf_realpath(struct strbuf *resolved, const char *path,
                      int die_on_error);
+char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path,
+                               int die_on_error);
 char *real_pathdup(const char *path, int die_on_error);
 const char *absolute_path(const char *path);
 char *absolute_pathdup(const char *path);
index fe1fa3dc41fe787883752671e5954b75616fe9ac..f128f18a9b0676de396e725e35d7df27df7d196f 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -544,6 +544,17 @@ struct commit_list *commit_list_insert(struct commit *item, struct commit_list *
        return new_list;
 }
 
+int commit_list_contains(struct commit *item, struct commit_list *list)
+{
+       while (list) {
+               if (list->item == item)
+                       return 1;
+               list = list->next;
+       }
+
+       return 0;
+}
+
 unsigned commit_list_count(const struct commit_list *l)
 {
        unsigned c = 0;
@@ -563,6 +574,17 @@ struct commit_list *copy_commit_list(struct commit_list *list)
        return head;
 }
 
+struct commit_list *reverse_commit_list(struct commit_list *list)
+{
+       struct commit_list *next = NULL, *current, *backup;
+       for (current = list; current; current = backup) {
+               backup = current->next;
+               current->next = next;
+               next = current;
+       }
+       return next;
+}
+
 void free_commit_list(struct commit_list *list)
 {
        while (list)
index 5467786c7be332299e54a954eb9af55b64d58848..f4e7b0158e2595ec203ce4bc0fc14cd95192ab72 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -167,6 +167,8 @@ int find_commit_subject(const char *commit_buffer, const char **subject);
 
 struct commit_list *commit_list_insert(struct commit *item,
                                        struct commit_list **list);
+int commit_list_contains(struct commit *item,
+                        struct commit_list *list);
 struct commit_list **commit_list_append(struct commit *commit,
                                        struct commit_list **next);
 unsigned commit_list_count(const struct commit_list *l);
@@ -177,6 +179,9 @@ void commit_list_sort_by_date(struct commit_list **list);
 /* Shallow copy of the input list */
 struct commit_list *copy_commit_list(struct commit_list *list);
 
+/* Modify list in-place to reverse it, returning new head; list will be tail */
+struct commit_list *reverse_commit_list(struct commit_list *list);
+
 void free_commit_list(struct commit_list *list);
 
 struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
index 1137bd73aff07c77e9b73a0b4c1d193cd38d88de..4c0cf3a1c15d7b804110c8fa6d52d174e5bfa4df 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1217,6 +1217,8 @@ static int git_default_core_config(const char *var, const char *value, void *cb)
                        return config_error_nonbool(var);
                if (!strcasecmp(value, "auto"))
                        default_abbrev = -1;
+               else if (!git_parse_maybe_bool_text(value))
+                       default_abbrev = the_hash_algo->hexsz;
                else {
                        int abbrev = git_config_int(var, value);
                        if (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)
index d8cec585af97e66e0534c1cebd32f2cdf022d382..0d31cdc866c77dfaab69b39305213497c49a120d 100644 (file)
@@ -35,18 +35,26 @@ struct bitmap *bitmap_new(void)
        return bitmap_word_alloc(32);
 }
 
+struct bitmap *bitmap_dup(const struct bitmap *src)
+{
+       struct bitmap *dst = bitmap_word_alloc(src->word_alloc);
+       COPY_ARRAY(dst->words, src->words, src->word_alloc);
+       return dst;
+}
+
+static void bitmap_grow(struct bitmap *self, size_t word_alloc)
+{
+       size_t old_size = self->word_alloc;
+       ALLOC_GROW(self->words, word_alloc, self->word_alloc);
+       memset(self->words + old_size, 0x0,
+              (self->word_alloc - old_size) * sizeof(eword_t));
+}
+
 void bitmap_set(struct bitmap *self, size_t pos)
 {
        size_t block = EWAH_BLOCK(pos);
 
-       if (block >= self->word_alloc) {
-               size_t old_size = self->word_alloc;
-               self->word_alloc = block ? block * 2 : 1;
-               REALLOC_ARRAY(self->words, self->word_alloc);
-               memset(self->words + old_size, 0x0,
-                       (self->word_alloc - old_size) * sizeof(eword_t));
-       }
-
+       bitmap_grow(self, block + 1);
        self->words[block] |= EWAH_MASK(pos);
 }
 
@@ -121,6 +129,15 @@ void bitmap_and_not(struct bitmap *self, struct bitmap *other)
                self->words[i] &= ~other->words[i];
 }
 
+void bitmap_or(struct bitmap *self, const struct bitmap *other)
+{
+       size_t i;
+
+       bitmap_grow(self, other->word_alloc);
+       for (i = 0; i < other->word_alloc; i++)
+               self->words[i] |= other->words[i];
+}
+
 void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other)
 {
        size_t original_size = self->word_alloc;
@@ -178,6 +195,27 @@ int bitmap_equals(struct bitmap *self, struct bitmap *other)
        return 1;
 }
 
+int bitmap_is_subset(struct bitmap *self, struct bitmap *other)
+{
+       size_t common_size, i;
+
+       if (self->word_alloc < other->word_alloc)
+               common_size = self->word_alloc;
+       else {
+               common_size = other->word_alloc;
+               for (i = common_size; i < self->word_alloc; i++) {
+                       if (self->words[i])
+                               return 1;
+               }
+       }
+
+       for (i = 0; i < common_size; i++) {
+               if (self->words[i] & ~other->words[i])
+                       return 1;
+       }
+       return 0;
+}
+
 void bitmap_reset(struct bitmap *bitmap)
 {
        memset(bitmap->words, 0x0, bitmap->word_alloc * sizeof(eword_t));
index d59b1afe3d6a83b89dc30e19e61f8a3e9b1cc0aa..2a8c7c5c33ab864788baaa7b6a52a5c59f84f7d0 100644 (file)
@@ -19,6 +19,7 @@
 #include "git-compat-util.h"
 #include "ewok.h"
 #include "ewok_rlw.h"
+#include "cache.h"
 
 static inline size_t min_size(size_t a, size_t b)
 {
@@ -33,20 +34,13 @@ static inline size_t max_size(size_t a, size_t b)
 static inline void buffer_grow(struct ewah_bitmap *self, size_t new_size)
 {
        size_t rlw_offset = (uint8_t *)self->rlw - (uint8_t *)self->buffer;
-
-       if (self->alloc_size >= new_size)
-               return;
-
-       self->alloc_size = new_size;
-       REALLOC_ARRAY(self->buffer, self->alloc_size);
+       ALLOC_GROW(self->buffer, new_size, self->alloc_size);
        self->rlw = self->buffer + (rlw_offset / sizeof(eword_t));
 }
 
 static inline void buffer_push(struct ewah_bitmap *self, eword_t value)
 {
-       if (self->buffer_size + 1 >= self->alloc_size)
-               buffer_grow(self, self->buffer_size * 3 / 2);
-
+       buffer_grow(self, self->buffer_size + 1);
        self->buffer[self->buffer_size++] = value;
 }
 
@@ -137,8 +131,7 @@ void ewah_add_dirty_words(
 
                rlw_set_literal_words(self->rlw, literals + can_add);
 
-               if (self->buffer_size + can_add >= self->alloc_size)
-                       buffer_grow(self, (self->buffer_size + can_add) * 3 / 2);
+               buffer_grow(self, self->buffer_size + can_add);
 
                if (negate) {
                        size_t i;
index 011852bef179191b867b96e9d0fb57b099bc60b3..66920965da19ab4a38db18d1b18e0f5fbaa4776f 100644 (file)
@@ -173,13 +173,14 @@ struct bitmap {
 
 struct bitmap *bitmap_new(void);
 struct bitmap *bitmap_word_alloc(size_t word_alloc);
+struct bitmap *bitmap_dup(const struct bitmap *src);
 void bitmap_set(struct bitmap *self, size_t pos);
 void bitmap_unset(struct bitmap *self, size_t pos);
 int bitmap_get(struct bitmap *self, size_t pos);
 void bitmap_reset(struct bitmap *self);
 void bitmap_free(struct bitmap *self);
 int bitmap_equals(struct bitmap *self, struct bitmap *other);
-int bitmap_is_subset(struct bitmap *self, struct bitmap *super);
+int bitmap_is_subset(struct bitmap *self, struct bitmap *other);
 
 struct ewah_bitmap * bitmap_to_ewah(struct bitmap *bitmap);
 struct bitmap *ewah_to_bitmap(struct ewah_bitmap *ewah);
index b487901d3ec524c5337c55b7b93ef86f561d36a1..31103d214074f388af870a5cd400d657da93c0b7 100644 (file)
 #include "cache.h"
 #include "merge-ort.h"
 
+#include "alloc.h"
+#include "blob.h"
+#include "cache-tree.h"
+#include "commit.h"
+#include "commit-reach.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "dir.h"
+#include "object-store.h"
+#include "strmap.h"
+#include "tree.h"
+#include "unpack-trees.h"
+#include "xdiff-interface.h"
+
+/*
+ * We have many arrays of size 3.  Whenever we have such an array, the
+ * indices refer to one of the sides of the three-way merge.  This is so
+ * pervasive that the constants 0, 1, and 2 are used in many places in the
+ * code (especially in arithmetic operations to find the other side's index
+ * or to compute a relevant mask), but sometimes these enum names are used
+ * to aid code clarity.
+ *
+ * See also 'filemask' and 'dirmask' in struct conflict_info; the "ith side"
+ * referred to there is one of these three sides.
+ */
+enum merge_side {
+       MERGE_BASE = 0,
+       MERGE_SIDE1 = 1,
+       MERGE_SIDE2 = 2
+};
+
+struct merge_options_internal {
+       /*
+        * paths: primary data structure in all of merge ort.
+        *
+        * The keys of paths:
+        *   * are full relative paths from the toplevel of the repository
+        *     (e.g. "drivers/firmware/raspberrypi.c").
+        *   * store all relevant paths in the repo, both directories and
+        *     files (e.g. drivers, drivers/firmware would also be included)
+        *   * these keys serve to intern all the path strings, which allows
+        *     us to do pointer comparison on directory names instead of
+        *     strcmp; we just have to be careful to use the interned strings.
+        *     (Technically paths_to_free may track some strings that were
+        *      removed from froms paths.)
+        *
+        * The values of paths:
+        *   * either a pointer to a merged_info, or a conflict_info struct
+        *   * merged_info contains all relevant information for a
+        *     non-conflicted entry.
+        *   * conflict_info contains a merged_info, plus any additional
+        *     information about a conflict such as the higher orders stages
+        *     involved and the names of the paths those came from (handy
+        *     once renames get involved).
+        *   * a path may start "conflicted" (i.e. point to a conflict_info)
+        *     and then a later step (e.g. three-way content merge) determines
+        *     it can be cleanly merged, at which point it'll be marked clean
+        *     and the algorithm will ignore any data outside the contained
+        *     merged_info for that entry
+        *   * If an entry remains conflicted, the merged_info portion of a
+        *     conflict_info will later be filled with whatever version of
+        *     the file should be placed in the working directory (e.g. an
+        *     as-merged-as-possible variation that contains conflict markers).
+        */
+       struct strmap paths;
+
+       /*
+        * conflicted: a subset of keys->values from "paths"
+        *
+        * conflicted is basically an optimization between process_entries()
+        * and record_conflicted_index_entries(); the latter could loop over
+        * ALL the entries in paths AGAIN and look for the ones that are
+        * still conflicted, but since process_entries() has to loop over
+        * all of them, it saves the ones it couldn't resolve in this strmap
+        * so that record_conflicted_index_entries() can iterate just the
+        * relevant entries.
+        */
+       struct strmap conflicted;
+
+       /*
+        * paths_to_free: additional list of strings to free
+        *
+        * If keys are removed from "paths", they are added to paths_to_free
+        * to ensure they are later freed.  We avoid free'ing immediately since
+        * other places (e.g. conflict_info.pathnames[]) may still be
+        * referencing these paths.
+        */
+       struct string_list paths_to_free;
+
+       /*
+        * output: special messages and conflict notices for various paths
+        *
+        * This is a map of pathnames (a subset of the keys in "paths" above)
+        * to strbufs.  It gathers various warning/conflict/notice messages
+        * for later processing.
+        */
+       struct strmap output;
+
+       /*
+        * current_dir_name: temporary var used in collect_merge_info_callback()
+        *
+        * Used to set merged_info.directory_name; see documentation for that
+        * variable and the requirements placed on that field.
+        */
+       const char *current_dir_name;
+
+       /* call_depth: recursion level counter for merging merge bases */
+       int call_depth;
+};
+
+struct version_info {
+       struct object_id oid;
+       unsigned short mode;
+};
+
+struct merged_info {
+       /* if is_null, ignore result.  otherwise result has oid & mode */
+       struct version_info result;
+       unsigned is_null:1;
+
+       /*
+        * clean: whether the path in question is cleanly merged.
+        *
+        * see conflict_info.merged for more details.
+        */
+       unsigned clean:1;
+
+       /*
+        * basename_offset: offset of basename of path.
+        *
+        * perf optimization to avoid recomputing offset of final '/'
+        * character in pathname (0 if no '/' in pathname).
+        */
+       size_t basename_offset;
+
+        /*
+         * directory_name: containing directory name.
+         *
+         * Note that we assume directory_name is constructed such that
+         *    strcmp(dir1_name, dir2_name) == 0 iff dir1_name == dir2_name,
+         * i.e. string equality is equivalent to pointer equality.  For this
+         * to hold, we have to be careful setting directory_name.
+         */
+       const char *directory_name;
+};
+
+struct conflict_info {
+       /*
+        * merged: the version of the path that will be written to working tree
+        *
+        * WARNING: It is critical to check merged.clean and ensure it is 0
+        * before reading any conflict_info fields outside of merged.
+        * Allocated merge_info structs will always have clean set to 1.
+        * Allocated conflict_info structs will have merged.clean set to 0
+        * initially.  The merged.clean field is how we know if it is safe
+        * to access other parts of conflict_info besides merged; if a
+        * conflict_info's merged.clean is changed to 1, the rest of the
+        * algorithm is not allowed to look at anything outside of the
+        * merged member anymore.
+        */
+       struct merged_info merged;
+
+       /* oids & modes from each of the three trees for this path */
+       struct version_info stages[3];
+
+       /* pathnames for each stage; may differ due to rename detection */
+       const char *pathnames[3];
+
+       /* Whether this path is/was involved in a directory/file conflict */
+       unsigned df_conflict:1;
+
+       /*
+        * Whether this path is/was involved in a non-content conflict other
+        * than a directory/file conflict (e.g. rename/rename, rename/delete,
+        * file location based on possible directory rename).
+        */
+       unsigned path_conflict:1;
+
+       /*
+        * For filemask and dirmask, the ith bit corresponds to whether the
+        * ith entry is a file (filemask) or a directory (dirmask).  Thus,
+        * filemask & dirmask is always zero, and filemask | dirmask is at
+        * most 7 but can be less when a path does not appear as either a
+        * file or a directory on at least one side of history.
+        *
+        * Note that these masks are related to enum merge_side, as the ith
+        * entry corresponds to side i.
+        *
+        * These values come from a traverse_trees() call; more info may be
+        * found looking at tree-walk.h's struct traverse_info,
+        * particularly the documentation above the "fn" member (note that
+        * filemask = mask & ~dirmask from that documentation).
+        */
+       unsigned filemask:3;
+       unsigned dirmask:3;
+
+       /*
+        * Optimization to track which stages match, to avoid the need to
+        * recompute it in multiple steps. Either 0 or at least 2 bits are
+        * set; if at least 2 bits are set, their corresponding stages match.
+        */
+       unsigned match_mask:3;
+};
+
+/*** Function Grouping: various utility functions ***/
+
+/*
+ * For the next three macros, see warning for conflict_info.merged.
+ *
+ * In each of the below, mi is a struct merged_info*, and ci was defined
+ * as a struct conflict_info* (but we need to verify ci isn't actually
+ * pointed at a struct merged_info*).
+ *
+ * INITIALIZE_CI: Assign ci to mi but only if it's safe; set to NULL otherwise.
+ * VERIFY_CI: Ensure that something we assigned to a conflict_info* is one.
+ * ASSIGN_AND_VERIFY_CI: Similar to VERIFY_CI but do assignment first.
+ */
+#define INITIALIZE_CI(ci, mi) do {                                           \
+       (ci) = (!(mi) || (mi)->clean) ? NULL : (struct conflict_info *)(mi); \
+} while (0)
+#define VERIFY_CI(ci) assert(ci && !ci->merged.clean);
+#define ASSIGN_AND_VERIFY_CI(ci, mi) do {    \
+       (ci) = (struct conflict_info *)(mi);  \
+       assert((ci) && !(mi)->clean);        \
+} while (0)
+
+static void free_strmap_strings(struct strmap *map)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *entry;
+
+       strmap_for_each_entry(map, &iter, entry) {
+               free((char*)entry->key);
+       }
+}
+
+static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
+                                         int reinitialize)
+{
+       void (*strmap_func)(struct strmap *, int) =
+               reinitialize ? strmap_partial_clear : strmap_clear;
+
+       /*
+        * We marked opti->paths with strdup_strings = 0, so that we
+        * wouldn't have to make another copy of the fullpath created by
+        * make_traverse_path from setup_path_info().  But, now that we've
+        * used it and have no other references to these strings, it is time
+        * to deallocate them.
+        */
+       free_strmap_strings(&opti->paths);
+       strmap_func(&opti->paths, 1);
+
+       /*
+        * All keys and values in opti->conflicted are a subset of those in
+        * opti->paths.  We don't want to deallocate anything twice, so we
+        * don't free the keys and we pass 0 for free_values.
+        */
+       strmap_func(&opti->conflicted, 0);
+
+       /*
+        * opti->paths_to_free is similar to opti->paths; we created it with
+        * strdup_strings = 0 to avoid making _another_ copy of the fullpath
+        * but now that we've used it and have no other references to these
+        * strings, it is time to deallocate them.  We do so by temporarily
+        * setting strdup_strings to 1.
+        */
+       opti->paths_to_free.strdup_strings = 1;
+       string_list_clear(&opti->paths_to_free, 0);
+       opti->paths_to_free.strdup_strings = 0;
+
+       if (!reinitialize) {
+               struct hashmap_iter iter;
+               struct strmap_entry *e;
+
+               /* Release and free each strbuf found in output */
+               strmap_for_each_entry(&opti->output, &iter, e) {
+                       struct strbuf *sb = e->value;
+                       strbuf_release(sb);
+                       /*
+                        * While strictly speaking we don't need to free(sb)
+                        * here because we could pass free_values=1 when
+                        * calling strmap_clear() on opti->output, that would
+                        * require strmap_clear to do another
+                        * strmap_for_each_entry() loop, so we just free it
+                        * while we're iterating anyway.
+                        */
+                       free(sb);
+               }
+               strmap_clear(&opti->output, 0);
+       }
+}
+
+static int err(struct merge_options *opt, const char *err, ...)
+{
+       va_list params;
+       struct strbuf sb = STRBUF_INIT;
+
+       strbuf_addstr(&sb, "error: ");
+       va_start(params, err);
+       strbuf_vaddf(&sb, err, params);
+       va_end(params);
+
+       error("%s", sb.buf);
+       strbuf_release(&sb);
+
+       return -1;
+}
+
+__attribute__((format (printf, 4, 5)))
+static void path_msg(struct merge_options *opt,
+                    const char *path,
+                    int omittable_hint, /* skippable under --remerge-diff */
+                    const char *fmt, ...)
+{
+       va_list ap;
+       struct strbuf *sb = strmap_get(&opt->priv->output, path);
+       if (!sb) {
+               sb = xmalloc(sizeof(*sb));
+               strbuf_init(sb, 0);
+               strmap_put(&opt->priv->output, path, sb);
+       }
+
+       va_start(ap, fmt);
+       strbuf_vaddf(sb, fmt, ap);
+       va_end(ap);
+
+       strbuf_addch(sb, '\n');
+}
+
+/*** Function Grouping: functions related to collect_merge_info() ***/
+
+static void setup_path_info(struct merge_options *opt,
+                           struct string_list_item *result,
+                           const char *current_dir_name,
+                           int current_dir_name_len,
+                           char *fullpath, /* we'll take over ownership */
+                           struct name_entry *names,
+                           struct name_entry *merged_version,
+                           unsigned is_null,     /* boolean */
+                           unsigned df_conflict, /* boolean */
+                           unsigned filemask,
+                           unsigned dirmask,
+                           int resolved          /* boolean */)
+{
+       /* result->util is void*, so mi is a convenience typed variable */
+       struct merged_info *mi;
+
+       assert(!is_null || resolved);
+       assert(!df_conflict || !resolved); /* df_conflict implies !resolved */
+       assert(resolved == (merged_version != NULL));
+
+       mi = xcalloc(1, resolved ? sizeof(struct merged_info) :
+                                  sizeof(struct conflict_info));
+       mi->directory_name = current_dir_name;
+       mi->basename_offset = current_dir_name_len;
+       mi->clean = !!resolved;
+       if (resolved) {
+               mi->result.mode = merged_version->mode;
+               oidcpy(&mi->result.oid, &merged_version->oid);
+               mi->is_null = !!is_null;
+       } else {
+               int i;
+               struct conflict_info *ci;
+
+               ASSIGN_AND_VERIFY_CI(ci, mi);
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                       ci->pathnames[i] = fullpath;
+                       ci->stages[i].mode = names[i].mode;
+                       oidcpy(&ci->stages[i].oid, &names[i].oid);
+               }
+               ci->filemask = filemask;
+               ci->dirmask = dirmask;
+               ci->df_conflict = !!df_conflict;
+               if (dirmask)
+                       /*
+                        * Assume is_null for now, but if we have entries
+                        * under the directory then when it is complete in
+                        * write_completed_directory() it'll update this.
+                        * Also, for D/F conflicts, we have to handle the
+                        * directory first, then clear this bit and process
+                        * the file to see how it is handled -- that occurs
+                        * near the top of process_entry().
+                        */
+                       mi->is_null = 1;
+       }
+       strmap_put(&opt->priv->paths, fullpath, mi);
+       result->string = fullpath;
+       result->util = mi;
+}
+
+static int collect_merge_info_callback(int n,
+                                      unsigned long mask,
+                                      unsigned long dirmask,
+                                      struct name_entry *names,
+                                      struct traverse_info *info)
+{
+       /*
+        * n is 3.  Always.
+        * common ancestor (mbase) has mask 1, and stored in index 0 of names
+        * head of side 1  (side1) has mask 2, and stored in index 1 of names
+        * head of side 2  (side2) has mask 4, and stored in index 2 of names
+        */
+       struct merge_options *opt = info->data;
+       struct merge_options_internal *opti = opt->priv;
+       struct string_list_item pi;  /* Path Info */
+       struct conflict_info *ci; /* typed alias to pi.util (which is void*) */
+       struct name_entry *p;
+       size_t len;
+       char *fullpath;
+       const char *dirname = opti->current_dir_name;
+       unsigned filemask = mask & ~dirmask;
+       unsigned match_mask = 0; /* will be updated below */
+       unsigned mbase_null = !(mask & 1);
+       unsigned side1_null = !(mask & 2);
+       unsigned side2_null = !(mask & 4);
+       unsigned side1_matches_mbase = (!side1_null && !mbase_null &&
+                                       names[0].mode == names[1].mode &&
+                                       oideq(&names[0].oid, &names[1].oid));
+       unsigned side2_matches_mbase = (!side2_null && !mbase_null &&
+                                       names[0].mode == names[2].mode &&
+                                       oideq(&names[0].oid, &names[2].oid));
+       unsigned sides_match = (!side1_null && !side2_null &&
+                               names[1].mode == names[2].mode &&
+                               oideq(&names[1].oid, &names[2].oid));
+
+       /*
+        * Note: When a path is a file on one side of history and a directory
+        * in another, we have a directory/file conflict.  In such cases, if
+        * the conflict doesn't resolve from renames and deletions, then we
+        * always leave directories where they are and move files out of the
+        * way.  Thus, while struct conflict_info has a df_conflict field to
+        * track such conflicts, we ignore that field for any directories at
+        * a path and only pay attention to it for files at the given path.
+        * The fact that we leave directories were they are also means that
+        * we do not need to worry about getting additional df_conflict
+        * information propagated from parent directories down to children
+        * (unlike, say traverse_trees_recursive() in unpack-trees.c, which
+        * sets a newinfo.df_conflicts field specifically to propagate it).
+        */
+       unsigned df_conflict = (filemask != 0) && (dirmask != 0);
+
+       /* n = 3 is a fundamental assumption. */
+       if (n != 3)
+               BUG("Called collect_merge_info_callback wrong");
+
+       /*
+        * A bunch of sanity checks verifying that traverse_trees() calls
+        * us the way I expect.  Could just remove these at some point,
+        * though maybe they are helpful to future code readers.
+        */
+       assert(mbase_null == is_null_oid(&names[0].oid));
+       assert(side1_null == is_null_oid(&names[1].oid));
+       assert(side2_null == is_null_oid(&names[2].oid));
+       assert(!mbase_null || !side1_null || !side2_null);
+       assert(mask > 0 && mask < 8);
+
+       /* Determine match_mask */
+       if (side1_matches_mbase)
+               match_mask = (side2_matches_mbase ? 7 : 3);
+       else if (side2_matches_mbase)
+               match_mask = 5;
+       else if (sides_match)
+               match_mask = 6;
+
+       /*
+        * Get the name of the relevant filepath, which we'll pass to
+        * setup_path_info() for tracking.
+        */
+       p = names;
+       while (!p->mode)
+               p++;
+       len = traverse_path_len(info, p->pathlen);
+
+       /* +1 in both of the following lines to include the NUL byte */
+       fullpath = xmalloc(len + 1);
+       make_traverse_path(fullpath, len + 1, info, p->path, p->pathlen);
+
+       /*
+        * If mbase, side1, and side2 all match, we can resolve early.  Even
+        * if these are trees, there will be no renames or anything
+        * underneath.
+        */
+       if (side1_matches_mbase && side2_matches_mbase) {
+               /* mbase, side1, & side2 all match; use mbase as resolution */
+               setup_path_info(opt, &pi, dirname, info->pathlen, fullpath,
+                               names, names+0, mbase_null, 0,
+                               filemask, dirmask, 1);
+               return mask;
+       }
+
+       /*
+        * Record information about the path so we can resolve later in
+        * process_entries.
+        */
+       setup_path_info(opt, &pi, dirname, info->pathlen, fullpath,
+                       names, NULL, 0, df_conflict, filemask, dirmask, 0);
+
+       ci = pi.util;
+       VERIFY_CI(ci);
+       ci->match_mask = match_mask;
+
+       /* If dirmask, recurse into subdirectories */
+       if (dirmask) {
+               struct traverse_info newinfo;
+               struct tree_desc t[3];
+               void *buf[3] = {NULL, NULL, NULL};
+               const char *original_dir_name;
+               int i, ret;
+
+               ci->match_mask &= filemask;
+               newinfo = *info;
+               newinfo.prev = info;
+               newinfo.name = p->path;
+               newinfo.namelen = p->pathlen;
+               newinfo.pathlen = st_add3(newinfo.pathlen, p->pathlen, 1);
+               /*
+                * If this directory we are about to recurse into cared about
+                * its parent directory (the current directory) having a D/F
+                * conflict, then we'd propagate the masks in this way:
+                *    newinfo.df_conflicts |= (mask & ~dirmask);
+                * But we don't worry about propagating D/F conflicts.  (See
+                * comment near setting of local df_conflict variable near
+                * the beginning of this function).
+                */
+
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                       if (i == 1 && side1_matches_mbase)
+                               t[1] = t[0];
+                       else if (i == 2 && side2_matches_mbase)
+                               t[2] = t[0];
+                       else if (i == 2 && sides_match)
+                               t[2] = t[1];
+                       else {
+                               const struct object_id *oid = NULL;
+                               if (dirmask & 1)
+                                       oid = &names[i].oid;
+                               buf[i] = fill_tree_descriptor(opt->repo,
+                                                             t + i, oid);
+                       }
+                       dirmask >>= 1;
+               }
+
+               original_dir_name = opti->current_dir_name;
+               opti->current_dir_name = pi.string;
+               ret = traverse_trees(NULL, 3, t, &newinfo);
+               opti->current_dir_name = original_dir_name;
+
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++)
+                       free(buf[i]);
+
+               if (ret < 0)
+                       return -1;
+       }
+
+       return mask;
+}
+
+static int collect_merge_info(struct merge_options *opt,
+                             struct tree *merge_base,
+                             struct tree *side1,
+                             struct tree *side2)
+{
+       int ret;
+       struct tree_desc t[3];
+       struct traverse_info info;
+       const char *toplevel_dir_placeholder = "";
+
+       opt->priv->current_dir_name = toplevel_dir_placeholder;
+       setup_traverse_info(&info, toplevel_dir_placeholder);
+       info.fn = collect_merge_info_callback;
+       info.data = opt;
+       info.show_all_errors = 1;
+
+       parse_tree(merge_base);
+       parse_tree(side1);
+       parse_tree(side2);
+       init_tree_desc(t + 0, merge_base->buffer, merge_base->size);
+       init_tree_desc(t + 1, side1->buffer, side1->size);
+       init_tree_desc(t + 2, side2->buffer, side2->size);
+
+       ret = traverse_trees(NULL, 3, t, &info);
+
+       return ret;
+}
+
+/*** Function Grouping: functions related to threeway content merges ***/
+
+static int handle_content_merge(struct merge_options *opt,
+                               const char *path,
+                               const struct version_info *o,
+                               const struct version_info *a,
+                               const struct version_info *b,
+                               const char *pathnames[3],
+                               const int extra_marker_size,
+                               struct version_info *result)
+{
+       die("Not yet implemented");
+}
+
+/*** Function Grouping: functions related to detect_and_process_renames(), ***
+ *** which are split into directory and regular rename detection sections. ***/
+
+/*** Function Grouping: functions related to directory rename detection ***/
+
+/*** Function Grouping: functions related to regular rename detection ***/
+
+static int detect_and_process_renames(struct merge_options *opt,
+                                     struct tree *merge_base,
+                                     struct tree *side1,
+                                     struct tree *side2)
+{
+       int clean = 1;
+
+       /*
+        * Rename detection works by detecting file similarity.  Here we use
+        * a really easy-to-implement scheme: files are similar IFF they have
+        * the same filename.  Therefore, by this scheme, there are no renames.
+        *
+        * TODO: Actually implement a real rename detection scheme.
+        */
+       return clean;
+}
+
+/*** Function Grouping: functions related to process_entries() ***/
+
+static int string_list_df_name_compare(const char *one, const char *two)
+{
+       int onelen = strlen(one);
+       int twolen = strlen(two);
+       /*
+        * Here we only care that entries for D/F conflicts are
+        * adjacent, in particular with the file of the D/F conflict
+        * appearing before files below the corresponding directory.
+        * The order of the rest of the list is irrelevant for us.
+        *
+        * To achieve this, we sort with df_name_compare and provide
+        * the mode S_IFDIR so that D/F conflicts will sort correctly.
+        * We use the mode S_IFDIR for everything else for simplicity,
+        * since in other cases any changes in their order due to
+        * sorting cause no problems for us.
+        */
+       int cmp = df_name_compare(one, onelen, S_IFDIR,
+                                 two, twolen, S_IFDIR);
+       /*
+        * Now that 'foo' and 'foo/bar' compare equal, we have to make sure
+        * that 'foo' comes before 'foo/bar'.
+        */
+       if (cmp)
+               return cmp;
+       return onelen - twolen;
+}
+
+struct directory_versions {
+       /*
+        * versions: list of (basename -> version_info)
+        *
+        * The basenames are in reverse lexicographic order of full pathnames,
+        * as processed in process_entries().  This puts all entries within
+        * a directory together, and covers the directory itself after
+        * everything within it, allowing us to write subtrees before needing
+        * to record information for the tree itself.
+        */
+       struct string_list versions;
+
+       /*
+        * offsets: list of (full relative path directories -> integer offsets)
+        *
+        * Since versions contains basenames from files in multiple different
+        * directories, we need to know which entries in versions correspond
+        * to which directories.  Values of e.g.
+        *     ""             0
+        *     src            2
+        *     src/moduleA    5
+        * Would mean that entries 0-1 of versions are files in the toplevel
+        * directory, entries 2-4 are files under src/, and the remaining
+        * entries starting at index 5 are files under src/moduleA/.
+        */
+       struct string_list offsets;
+
+       /*
+        * last_directory: directory that previously processed file found in
+        *
+        * last_directory starts NULL, but records the directory in which the
+        * previous file was found within.  As soon as
+        *    directory(current_file) != last_directory
+        * then we need to start updating accounting in versions & offsets.
+        * Note that last_directory is always the last path in "offsets" (or
+        * NULL if "offsets" is empty) so this exists just for quick access.
+        */
+       const char *last_directory;
+
+       /* last_directory_len: cached computation of strlen(last_directory) */
+       unsigned last_directory_len;
+};
+
+static int tree_entry_order(const void *a_, const void *b_)
+{
+       const struct string_list_item *a = a_;
+       const struct string_list_item *b = b_;
+
+       const struct merged_info *ami = a->util;
+       const struct merged_info *bmi = b->util;
+       return base_name_compare(a->string, strlen(a->string), ami->result.mode,
+                                b->string, strlen(b->string), bmi->result.mode);
+}
+
+static void write_tree(struct object_id *result_oid,
+                      struct string_list *versions,
+                      unsigned int offset,
+                      size_t hash_size)
+{
+       size_t maxlen = 0, extra;
+       unsigned int nr = versions->nr - offset;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list relevant_entries = STRING_LIST_INIT_NODUP;
+       int i;
+
+       /*
+        * We want to sort the last (versions->nr-offset) entries in versions.
+        * Do so by abusing the string_list API a bit: make another string_list
+        * that contains just those entries and then sort them.
+        *
+        * We won't use relevant_entries again and will let it just pop off the
+        * stack, so there won't be allocation worries or anything.
+        */
+       relevant_entries.items = versions->items + offset;
+       relevant_entries.nr = versions->nr - offset;
+       QSORT(relevant_entries.items, relevant_entries.nr, tree_entry_order);
+
+       /* Pre-allocate some space in buf */
+       extra = hash_size + 8; /* 8: 6 for mode, 1 for space, 1 for NUL char */
+       for (i = 0; i < nr; i++) {
+               maxlen += strlen(versions->items[offset+i].string) + extra;
+       }
+       strbuf_grow(&buf, maxlen);
+
+       /* Write each entry out to buf */
+       for (i = 0; i < nr; i++) {
+               struct merged_info *mi = versions->items[offset+i].util;
+               struct version_info *ri = &mi->result;
+               strbuf_addf(&buf, "%o %s%c",
+                           ri->mode,
+                           versions->items[offset+i].string, '\0');
+               strbuf_add(&buf, ri->oid.hash, hash_size);
+       }
+
+       /* Write this object file out, and record in result_oid */
+       write_object_file(buf.buf, buf.len, tree_type, result_oid);
+       strbuf_release(&buf);
+}
+
+static void record_entry_for_tree(struct directory_versions *dir_metadata,
+                                 const char *path,
+                                 struct merged_info *mi)
+{
+       const char *basename;
+
+       if (mi->is_null)
+               /* nothing to record */
+               return;
+
+       basename = path + mi->basename_offset;
+       assert(strchr(basename, '/') == NULL);
+       string_list_append(&dir_metadata->versions,
+                          basename)->util = &mi->result;
+}
+
+static void write_completed_directory(struct merge_options *opt,
+                                     const char *new_directory_name,
+                                     struct directory_versions *info)
+{
+       const char *prev_dir;
+       struct merged_info *dir_info = NULL;
+       unsigned int offset;
+
+       /*
+        * Some explanation of info->versions and info->offsets...
+        *
+        * process_entries() iterates over all relevant files AND
+        * directories in reverse lexicographic order, and calls this
+        * function.  Thus, an example of the paths that process_entries()
+        * could operate on (along with the directories for those paths
+        * being shown) is:
+        *
+        *     xtract.c             ""
+        *     tokens.txt           ""
+        *     src/moduleB/umm.c    src/moduleB
+        *     src/moduleB/stuff.h  src/moduleB
+        *     src/moduleB/baz.c    src/moduleB
+        *     src/moduleB          src
+        *     src/moduleA/foo.c    src/moduleA
+        *     src/moduleA/bar.c    src/moduleA
+        *     src/moduleA          src
+        *     src                  ""
+        *     Makefile             ""
+        *
+        * info->versions:
+        *
+        *     always contains the unprocessed entries and their
+        *     version_info information.  For example, after the first five
+        *     entries above, info->versions would be:
+        *
+        *         xtract.c     <xtract.c's version_info>
+        *         token.txt    <token.txt's version_info>
+        *         umm.c        <src/moduleB/umm.c's version_info>
+        *         stuff.h      <src/moduleB/stuff.h's version_info>
+        *         baz.c        <src/moduleB/baz.c's version_info>
+        *
+        *     Once a subdirectory is completed we remove the entries in
+        *     that subdirectory from info->versions, writing it as a tree
+        *     (write_tree()).  Thus, as soon as we get to src/moduleB,
+        *     info->versions would be updated to
+        *
+        *         xtract.c     <xtract.c's version_info>
+        *         token.txt    <token.txt's version_info>
+        *         moduleB      <src/moduleB's version_info>
+        *
+        * info->offsets:
+        *
+        *     helps us track which entries in info->versions correspond to
+        *     which directories.  When we are N directories deep (e.g. 4
+        *     for src/modA/submod/subdir/), we have up to N+1 unprocessed
+        *     directories (+1 because of toplevel dir).  Corresponding to
+        *     the info->versions example above, after processing five entries
+        *     info->offsets will be:
+        *
+        *         ""           0
+        *         src/moduleB  2
+        *
+        *     which is used to know that xtract.c & token.txt are from the
+        *     toplevel dirctory, while umm.c & stuff.h & baz.c are from the
+        *     src/moduleB directory.  Again, following the example above,
+        *     once we need to process src/moduleB, then info->offsets is
+        *     updated to
+        *
+        *         ""           0
+        *         src          2
+        *
+        *     which says that moduleB (and only moduleB so far) is in the
+        *     src directory.
+        *
+        *     One unique thing to note about info->offsets here is that
+        *     "src" was not added to info->offsets until there was a path
+        *     (a file OR directory) immediately below src/ that got
+        *     processed.
+        *
+        * Since process_entry() just appends new entries to info->versions,
+        * write_completed_directory() only needs to do work if the next path
+        * is in a directory that is different than the last directory found
+        * in info->offsets.
+        */
+
+       /*
+        * If we are working with the same directory as the last entry, there
+        * is no work to do.  (See comments above the directory_name member of
+        * struct merged_info for why we can use pointer comparison instead of
+        * strcmp here.)
+        */
+       if (new_directory_name == info->last_directory)
+               return;
+
+       /*
+        * If we are just starting (last_directory is NULL), or last_directory
+        * is a prefix of the current directory, then we can just update
+        * info->offsets to record the offset where we started this directory
+        * and update last_directory to have quick access to it.
+        */
+       if (info->last_directory == NULL ||
+           !strncmp(new_directory_name, info->last_directory,
+                    info->last_directory_len)) {
+               uintptr_t offset = info->versions.nr;
+
+               info->last_directory = new_directory_name;
+               info->last_directory_len = strlen(info->last_directory);
+               /*
+                * Record the offset into info->versions where we will
+                * start recording basenames of paths found within
+                * new_directory_name.
+                */
+               string_list_append(&info->offsets,
+                                  info->last_directory)->util = (void*)offset;
+               return;
+       }
+
+       /*
+        * The next entry that will be processed will be within
+        * new_directory_name.  Since at this point we know that
+        * new_directory_name is within a different directory than
+        * info->last_directory, we have all entries for info->last_directory
+        * in info->versions and we need to create a tree object for them.
+        */
+       dir_info = strmap_get(&opt->priv->paths, info->last_directory);
+       assert(dir_info);
+       offset = (uintptr_t)info->offsets.items[info->offsets.nr-1].util;
+       if (offset == info->versions.nr) {
+               /*
+                * Actually, we don't need to create a tree object in this
+                * case.  Whenever all files within a directory disappear
+                * during the merge (e.g. unmodified on one side and
+                * deleted on the other, or files were renamed elsewhere),
+                * then we get here and the directory itself needs to be
+                * omitted from its parent tree as well.
+                */
+               dir_info->is_null = 1;
+       } else {
+               /*
+                * Write out the tree to the git object directory, and also
+                * record the mode and oid in dir_info->result.
+                */
+               dir_info->is_null = 0;
+               dir_info->result.mode = S_IFDIR;
+               write_tree(&dir_info->result.oid, &info->versions, offset,
+                          opt->repo->hash_algo->rawsz);
+       }
+
+       /*
+        * We've now used several entries from info->versions and one entry
+        * from info->offsets, so we get rid of those values.
+        */
+       info->offsets.nr--;
+       info->versions.nr = offset;
+
+       /*
+        * Now we've taken care of the completed directory, but we need to
+        * prepare things since future entries will be in
+        * new_directory_name.  (In particular, process_entry() will be
+        * appending new entries to info->versions.)  So, we need to make
+        * sure new_directory_name is the last entry in info->offsets.
+        */
+       prev_dir = info->offsets.nr == 0 ? NULL :
+                  info->offsets.items[info->offsets.nr-1].string;
+       if (new_directory_name != prev_dir) {
+               uintptr_t c = info->versions.nr;
+               string_list_append(&info->offsets,
+                                  new_directory_name)->util = (void*)c;
+       }
+
+       /* And, of course, we need to update last_directory to match. */
+       info->last_directory = new_directory_name;
+       info->last_directory_len = strlen(info->last_directory);
+}
+
+/* Per entry merge function */
+static void process_entry(struct merge_options *opt,
+                         const char *path,
+                         struct conflict_info *ci,
+                         struct directory_versions *dir_metadata)
+{
+       VERIFY_CI(ci);
+       assert(ci->filemask >= 0 && ci->filemask <= 7);
+       /* ci->match_mask == 7 was handled in collect_merge_info_callback() */
+       assert(ci->match_mask == 0 || ci->match_mask == 3 ||
+              ci->match_mask == 5 || ci->match_mask == 6);
+
+       if (ci->dirmask) {
+               record_entry_for_tree(dir_metadata, path, &ci->merged);
+               if (ci->filemask == 0)
+                       /* nothing else to handle */
+                       return;
+               assert(ci->df_conflict);
+       }
+
+       if (ci->df_conflict) {
+               die("Not yet implemented.");
+       }
+
+       /*
+        * NOTE: Below there is a long switch-like if-elseif-elseif... block
+        *       which the code goes through even for the df_conflict cases
+        *       above.  Well, it will once we don't die-not-implemented above.
+        */
+       if (ci->match_mask) {
+               ci->merged.clean = 1;
+               if (ci->match_mask == 6) {
+                       /* stages[1] == stages[2] */
+                       ci->merged.result.mode = ci->stages[1].mode;
+                       oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
+               } else {
+                       /* determine the mask of the side that didn't match */
+                       unsigned int othermask = 7 & ~ci->match_mask;
+                       int side = (othermask == 4) ? 2 : 1;
+
+                       ci->merged.result.mode = ci->stages[side].mode;
+                       ci->merged.is_null = !ci->merged.result.mode;
+                       oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
+
+                       assert(othermask == 2 || othermask == 4);
+                       assert(ci->merged.is_null ==
+                              (ci->filemask == ci->match_mask));
+               }
+       } else if (ci->filemask >= 6 &&
+                  (S_IFMT & ci->stages[1].mode) !=
+                  (S_IFMT & ci->stages[2].mode)) {
+               /*
+                * Two different items from (file/submodule/symlink)
+                */
+               die("Not yet implemented.");
+       } else if (ci->filemask >= 6) {
+               /*
+                * TODO: Needs a two-way or three-way content merge, but we're
+                * just being lazy and copying the version from HEAD and
+                * leaving it as conflicted.
+                */
+               ci->merged.clean = 0;
+               ci->merged.result.mode = ci->stages[1].mode;
+               oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
+               /* When we fix above, we'll call handle_content_merge() */
+               (void)handle_content_merge;
+       } else if (ci->filemask == 3 || ci->filemask == 5) {
+               /* Modify/delete */
+               const char *modify_branch, *delete_branch;
+               int side = (ci->filemask == 5) ? 2 : 1;
+               int index = opt->priv->call_depth ? 0 : side;
+
+               ci->merged.result.mode = ci->stages[index].mode;
+               oidcpy(&ci->merged.result.oid, &ci->stages[index].oid);
+               ci->merged.clean = 0;
+
+               modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
+               delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
+
+               path_msg(opt, path, 0,
+                        _("CONFLICT (modify/delete): %s deleted in %s "
+                          "and modified in %s.  Version %s of %s left "
+                          "in tree."),
+                        path, delete_branch, modify_branch,
+                        modify_branch, path);
+       } else if (ci->filemask == 2 || ci->filemask == 4) {
+               /* Added on one side */
+               int side = (ci->filemask == 4) ? 2 : 1;
+               ci->merged.result.mode = ci->stages[side].mode;
+               oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
+               ci->merged.clean = !ci->df_conflict;
+       } else if (ci->filemask == 1) {
+               /* Deleted on both sides */
+               ci->merged.is_null = 1;
+               ci->merged.result.mode = 0;
+               oidcpy(&ci->merged.result.oid, &null_oid);
+               ci->merged.clean = 1;
+       }
+
+       /*
+        * If still conflicted, record it separately.  This allows us to later
+        * iterate over just conflicted entries when updating the index instead
+        * of iterating over all entries.
+        */
+       if (!ci->merged.clean)
+               strmap_put(&opt->priv->conflicted, path, ci);
+       record_entry_for_tree(dir_metadata, path, &ci->merged);
+}
+
+static void process_entries(struct merge_options *opt,
+                           struct object_id *result_oid)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *e;
+       struct string_list plist = STRING_LIST_INIT_NODUP;
+       struct string_list_item *entry;
+       struct directory_versions dir_metadata = { STRING_LIST_INIT_NODUP,
+                                                  STRING_LIST_INIT_NODUP,
+                                                  NULL, 0 };
+
+       if (strmap_empty(&opt->priv->paths)) {
+               oidcpy(result_oid, opt->repo->hash_algo->empty_tree);
+               return;
+       }
+
+       /* Hack to pre-allocate plist to the desired size */
+       ALLOC_GROW(plist.items, strmap_get_size(&opt->priv->paths), plist.alloc);
+
+       /* Put every entry from paths into plist, then sort */
+       strmap_for_each_entry(&opt->priv->paths, &iter, e) {
+               string_list_append(&plist, e->key)->util = e->value;
+       }
+       plist.cmp = string_list_df_name_compare;
+       string_list_sort(&plist);
+
+       /*
+        * Iterate over the items in reverse order, so we can handle paths
+        * below a directory before needing to handle the directory itself.
+        *
+        * This allows us to write subtrees before we need to write trees,
+        * and it also enables sane handling of directory/file conflicts
+        * (because it allows us to know whether the directory is still in
+        * the way when it is time to process the file at the same path).
+        */
+       for (entry = &plist.items[plist.nr-1]; entry >= plist.items; --entry) {
+               char *path = entry->string;
+               /*
+                * NOTE: mi may actually be a pointer to a conflict_info, but
+                * we have to check mi->clean first to see if it's safe to
+                * reassign to such a pointer type.
+                */
+               struct merged_info *mi = entry->util;
+
+               write_completed_directory(opt, mi->directory_name,
+                                         &dir_metadata);
+               if (mi->clean)
+                       record_entry_for_tree(&dir_metadata, path, mi);
+               else {
+                       struct conflict_info *ci = (struct conflict_info *)mi;
+                       process_entry(opt, path, ci, &dir_metadata);
+               }
+       }
+
+       if (dir_metadata.offsets.nr != 1 ||
+           (uintptr_t)dir_metadata.offsets.items[0].util != 0) {
+               printf("dir_metadata.offsets.nr = %d (should be 1)\n",
+                      dir_metadata.offsets.nr);
+               printf("dir_metadata.offsets.items[0].util = %u (should be 0)\n",
+                      (unsigned)(uintptr_t)dir_metadata.offsets.items[0].util);
+               fflush(stdout);
+               BUG("dir_metadata accounting completely off; shouldn't happen");
+       }
+       write_tree(result_oid, &dir_metadata.versions, 0,
+                  opt->repo->hash_algo->rawsz);
+       string_list_clear(&plist, 0);
+       string_list_clear(&dir_metadata.versions, 0);
+       string_list_clear(&dir_metadata.offsets, 0);
+}
+
+/*** Function Grouping: functions related to merge_switch_to_result() ***/
+
+static int checkout(struct merge_options *opt,
+                   struct tree *prev,
+                   struct tree *next)
+{
+       /* Switch the index/working copy from old to new */
+       int ret;
+       struct tree_desc trees[2];
+       struct unpack_trees_options unpack_opts;
+
+       memset(&unpack_opts, 0, sizeof(unpack_opts));
+       unpack_opts.head_idx = -1;
+       unpack_opts.src_index = opt->repo->index;
+       unpack_opts.dst_index = opt->repo->index;
+
+       setup_unpack_trees_porcelain(&unpack_opts, "merge");
+
+       /*
+        * NOTE: if this were just "git checkout" code, we would probably
+        * read or refresh the cache and check for a conflicted index, but
+        * builtin/merge.c or sequencer.c really needs to read the index
+        * and check for conflicted entries before starting merging for a
+        * good user experience (no sense waiting for merges/rebases before
+        * erroring out), so there's no reason to duplicate that work here.
+        */
+
+       /* 2-way merge to the new branch */
+       unpack_opts.update = 1;
+       unpack_opts.merge = 1;
+       unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */
+       unpack_opts.verbose_update = (opt->verbosity > 2);
+       unpack_opts.fn = twoway_merge;
+       if (1/* FIXME: opts->overwrite_ignore*/) {
+               unpack_opts.dir = xcalloc(1, sizeof(*unpack_opts.dir));
+               unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
+               setup_standard_excludes(unpack_opts.dir);
+       }
+       parse_tree(prev);
+       init_tree_desc(&trees[0], prev->buffer, prev->size);
+       parse_tree(next);
+       init_tree_desc(&trees[1], next->buffer, next->size);
+
+       ret = unpack_trees(2, trees, &unpack_opts);
+       clear_unpack_trees_porcelain(&unpack_opts);
+       dir_clear(unpack_opts.dir);
+       FREE_AND_NULL(unpack_opts.dir);
+       return ret;
+}
+
+static int record_conflicted_index_entries(struct merge_options *opt,
+                                          struct index_state *index,
+                                          struct strmap *paths,
+                                          struct strmap *conflicted)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *e;
+       int errs = 0;
+       int original_cache_nr;
+
+       if (strmap_empty(conflicted))
+               return 0;
+
+       original_cache_nr = index->cache_nr;
+
+       /* Put every entry from paths into plist, then sort */
+       strmap_for_each_entry(conflicted, &iter, e) {
+               const char *path = e->key;
+               struct conflict_info *ci = e->value;
+               int pos;
+               struct cache_entry *ce;
+               int i;
+
+               VERIFY_CI(ci);
+
+               /*
+                * The index will already have a stage=0 entry for this path,
+                * because we created an as-merged-as-possible version of the
+                * file and checkout() moved the working copy and index over
+                * to that version.
+                *
+                * However, previous iterations through this loop will have
+                * added unstaged entries to the end of the cache which
+                * ignore the standard alphabetical ordering of cache
+                * entries and break invariants needed for index_name_pos()
+                * to work.  However, we know the entry we want is before
+                * those appended cache entries, so do a temporary swap on
+                * cache_nr to only look through entries of interest.
+                */
+               SWAP(index->cache_nr, original_cache_nr);
+               pos = index_name_pos(index, path, strlen(path));
+               SWAP(index->cache_nr, original_cache_nr);
+               if (pos < 0) {
+                       if (ci->filemask != 1)
+                               BUG("Conflicted %s but nothing in basic working tree or index; this shouldn't happen", path);
+                       cache_tree_invalidate_path(index, path);
+               } else {
+                       ce = index->cache[pos];
+
+                       /*
+                        * Clean paths with CE_SKIP_WORKTREE set will not be
+                        * written to the working tree by the unpack_trees()
+                        * call in checkout().  Our conflicted entries would
+                        * have appeared clean to that code since we ignored
+                        * the higher order stages.  Thus, we need override
+                        * the CE_SKIP_WORKTREE bit and manually write those
+                        * files to the working disk here.
+                        *
+                        * TODO: Implement this CE_SKIP_WORKTREE fixup.
+                        */
+
+                       /*
+                        * Mark this cache entry for removal and instead add
+                        * new stage>0 entries corresponding to the
+                        * conflicts.  If there are many conflicted entries, we
+                        * want to avoid memmove'ing O(NM) entries by
+                        * inserting the new entries one at a time.  So,
+                        * instead, we just add the new cache entries to the
+                        * end (ignoring normal index requirements on sort
+                        * order) and sort the index once we're all done.
+                        */
+                       ce->ce_flags |= CE_REMOVE;
+               }
+
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                       struct version_info *vi;
+                       if (!(ci->filemask & (1ul << i)))
+                               continue;
+                       vi = &ci->stages[i];
+                       ce = make_cache_entry(index, vi->mode, &vi->oid,
+                                             path, i+1, 0);
+                       add_index_entry(index, ce, ADD_CACHE_JUST_APPEND);
+               }
+       }
+
+       /*
+        * Remove the unused cache entries (and invalidate the relevant
+        * cache-trees), then sort the index entries to get the conflicted
+        * entries we added to the end into their right locations.
+        */
+       remove_marked_cache_entries(index, 1);
+       QSORT(index->cache, index->cache_nr, cmp_cache_name_compare);
+
+       return errs;
+}
+
 void merge_switch_to_result(struct merge_options *opt,
                            struct tree *head,
                            struct merge_result *result,
                            int update_worktree_and_index,
                            int display_update_msgs)
 {
-       die("Not yet implemented");
+       assert(opt->priv == NULL);
+       if (result->clean >= 0 && update_worktree_and_index) {
+               struct merge_options_internal *opti = result->priv;
+
+               if (checkout(opt, head, result->tree)) {
+                       /* failure to function */
+                       result->clean = -1;
+                       return;
+               }
+
+               if (record_conflicted_index_entries(opt, opt->repo->index,
+                                                   &opti->paths,
+                                                   &opti->conflicted)) {
+                       /* failure to function */
+                       result->clean = -1;
+                       return;
+               }
+       }
+
+       if (display_update_msgs) {
+               struct merge_options_internal *opti = result->priv;
+               struct hashmap_iter iter;
+               struct strmap_entry *e;
+               struct string_list olist = STRING_LIST_INIT_NODUP;
+               int i;
+
+               /* Hack to pre-allocate olist to the desired size */
+               ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
+                          olist.alloc);
+
+               /* Put every entry from output into olist, then sort */
+               strmap_for_each_entry(&opti->output, &iter, e) {
+                       string_list_append(&olist, e->key)->util = e->value;
+               }
+               string_list_sort(&olist);
+
+               /* Iterate over the items, printing them */
+               for (i = 0; i < olist.nr; ++i) {
+                       struct strbuf *sb = olist.items[i].util;
+
+                       printf("%s", sb->buf);
+               }
+               string_list_clear(&olist, 0);
+       }
+
        merge_finalize(opt, result);
 }
 
 void merge_finalize(struct merge_options *opt,
                    struct merge_result *result)
 {
-       die("Not yet implemented");
+       struct merge_options_internal *opti = result->priv;
+
+       assert(opt->priv == NULL);
+
+       clear_or_reinit_internal_opts(opti, 0);
+       FREE_AND_NULL(opti);
+}
+
+/*** Function Grouping: helper functions for merge_incore_*() ***/
+
+static inline void set_commit_tree(struct commit *c, struct tree *t)
+{
+       c->maybe_tree = t;
+}
+
+static struct commit *make_virtual_commit(struct repository *repo,
+                                         struct tree *tree,
+                                         const char *comment)
+{
+       struct commit *commit = alloc_commit_node(repo);
+
+       set_merge_remote_desc(commit, comment, (struct object *)commit);
+       set_commit_tree(commit, tree);
+       commit->object.parsed = 1;
+       return commit;
+}
+
+static void merge_start(struct merge_options *opt, struct merge_result *result)
+{
+       /* Sanity checks on opt */
+       assert(opt->repo);
+
+       assert(opt->branch1 && opt->branch2);
+
+       assert(opt->detect_directory_renames >= MERGE_DIRECTORY_RENAMES_NONE &&
+              opt->detect_directory_renames <= MERGE_DIRECTORY_RENAMES_TRUE);
+       assert(opt->rename_limit >= -1);
+       assert(opt->rename_score >= 0 && opt->rename_score <= MAX_SCORE);
+       assert(opt->show_rename_progress >= 0 && opt->show_rename_progress <= 1);
+
+       assert(opt->xdl_opts >= 0);
+       assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
+              opt->recursive_variant <= MERGE_VARIANT_THEIRS);
+
+       /*
+        * detect_renames, verbosity, buffer_output, and obuf are ignored
+        * fields that were used by "recursive" rather than "ort" -- but
+        * sanity check them anyway.
+        */
+       assert(opt->detect_renames >= -1 &&
+              opt->detect_renames <= DIFF_DETECT_COPY);
+       assert(opt->verbosity >= 0 && opt->verbosity <= 5);
+       assert(opt->buffer_output <= 2);
+       assert(opt->obuf.len == 0);
+
+       assert(opt->priv == NULL);
+
+       /* Default to histogram diff.  Actually, just hardcode it...for now. */
+       opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF);
+
+       /* Initialization of opt->priv, our internal merge data */
+       opt->priv = xcalloc(1, sizeof(*opt->priv));
+
+       /*
+        * Although we initialize opt->priv->paths with strdup_strings=0,
+        * that's just to avoid making yet another copy of an allocated
+        * string.  Putting the entry into paths means we are taking
+        * ownership, so we will later free it.  paths_to_free is similar.
+        *
+        * In contrast, conflicted just has a subset of keys from paths, so
+        * we don't want to free those (it'd be a duplicate free).
+        */
+       strmap_init_with_options(&opt->priv->paths, NULL, 0);
+       strmap_init_with_options(&opt->priv->conflicted, NULL, 0);
+       string_list_init(&opt->priv->paths_to_free, 0);
+
+       /*
+        * keys & strbufs in output will sometimes need to outlive "paths",
+        * so it will have a copy of relevant keys.  It's probably a small
+        * subset of the overall paths that have special output.
+        */
+       strmap_init(&opt->priv->output);
+}
+
+/*** Function Grouping: merge_incore_*() and their internal variants ***/
+
+/*
+ * Originally from merge_trees_internal(); heavily adapted, though.
+ */
+static void merge_ort_nonrecursive_internal(struct merge_options *opt,
+                                           struct tree *merge_base,
+                                           struct tree *side1,
+                                           struct tree *side2,
+                                           struct merge_result *result)
+{
+       struct object_id working_tree_oid;
+
+       if (collect_merge_info(opt, merge_base, side1, side2) != 0) {
+               /*
+                * TRANSLATORS: The %s arguments are: 1) tree hash of a merge
+                * base, and 2-3) the trees for the two trees we're merging.
+                */
+               err(opt, _("collecting merge info failed for trees %s, %s, %s"),
+                   oid_to_hex(&merge_base->object.oid),
+                   oid_to_hex(&side1->object.oid),
+                   oid_to_hex(&side2->object.oid));
+               result->clean = -1;
+               return;
+       }
+
+       result->clean = detect_and_process_renames(opt, merge_base,
+                                                  side1, side2);
+       process_entries(opt, &working_tree_oid);
+
+       /* Set return values */
+       result->tree = parse_tree_indirect(&working_tree_oid);
+       /* existence of conflicted entries implies unclean */
+       result->clean &= strmap_empty(&opt->priv->conflicted);
+       if (!opt->priv->call_depth) {
+               result->priv = opt->priv;
+               opt->priv = NULL;
+       }
+}
+
+/*
+ * Originally from merge_recursive_internal(); somewhat adapted, though.
+ */
+static void merge_ort_internal(struct merge_options *opt,
+                              struct commit_list *merge_bases,
+                              struct commit *h1,
+                              struct commit *h2,
+                              struct merge_result *result)
+{
+       struct commit_list *iter;
+       struct commit *merged_merge_bases;
+       const char *ancestor_name;
+       struct strbuf merge_base_abbrev = STRBUF_INIT;
+
+       if (!merge_bases) {
+               merge_bases = get_merge_bases(h1, h2);
+               /* See merge-ort.h:merge_incore_recursive() declaration NOTE */
+               merge_bases = reverse_commit_list(merge_bases);
+       }
+
+       merged_merge_bases = pop_commit(&merge_bases);
+       if (merged_merge_bases == NULL) {
+               /* if there is no common ancestor, use an empty tree */
+               struct tree *tree;
+
+               tree = lookup_tree(opt->repo, opt->repo->hash_algo->empty_tree);
+               merged_merge_bases = make_virtual_commit(opt->repo, tree,
+                                                        "ancestor");
+               ancestor_name = "empty tree";
+       } else if (merge_bases) {
+               ancestor_name = "merged common ancestors";
+       } else {
+               strbuf_add_unique_abbrev(&merge_base_abbrev,
+                                        &merged_merge_bases->object.oid,
+                                        DEFAULT_ABBREV);
+               ancestor_name = merge_base_abbrev.buf;
+       }
+
+       for (iter = merge_bases; iter; iter = iter->next) {
+               const char *saved_b1, *saved_b2;
+               struct commit *prev = merged_merge_bases;
+
+               opt->priv->call_depth++;
+               /*
+                * When the merge fails, the result contains files
+                * with conflict markers. The cleanness flag is
+                * ignored (unless indicating an error), it was never
+                * actually used, as result of merge_trees has always
+                * overwritten it: the committed "conflicts" were
+                * already resolved.
+                */
+               saved_b1 = opt->branch1;
+               saved_b2 = opt->branch2;
+               opt->branch1 = "Temporary merge branch 1";
+               opt->branch2 = "Temporary merge branch 2";
+               merge_ort_internal(opt, NULL, prev, iter->item, result);
+               if (result->clean < 0)
+                       return;
+               opt->branch1 = saved_b1;
+               opt->branch2 = saved_b2;
+               opt->priv->call_depth--;
+
+               merged_merge_bases = make_virtual_commit(opt->repo,
+                                                        result->tree,
+                                                        "merged tree");
+               commit_list_insert(prev, &merged_merge_bases->parents);
+               commit_list_insert(iter->item,
+                                  &merged_merge_bases->parents->next);
+
+               clear_or_reinit_internal_opts(opt->priv, 1);
+       }
+
+       opt->ancestor = ancestor_name;
+       merge_ort_nonrecursive_internal(opt,
+                                       repo_get_commit_tree(opt->repo,
+                                                            merged_merge_bases),
+                                       repo_get_commit_tree(opt->repo, h1),
+                                       repo_get_commit_tree(opt->repo, h2),
+                                       result);
+       strbuf_release(&merge_base_abbrev);
+       opt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */
 }
 
 void merge_incore_nonrecursive(struct merge_options *opt,
@@ -39,7 +1554,9 @@ void merge_incore_nonrecursive(struct merge_options *opt,
                               struct tree *side2,
                               struct merge_result *result)
 {
-       die("Not yet implemented");
+       assert(opt->ancestor != NULL);
+       merge_start(opt, result);
+       merge_ort_nonrecursive_internal(opt, merge_base, side1, side2, result);
 }
 
 void merge_incore_recursive(struct merge_options *opt,
@@ -48,5 +1565,9 @@ void merge_incore_recursive(struct merge_options *opt,
                            struct commit *side2,
                            struct merge_result *result)
 {
-       die("Not yet implemented");
+       /* We set the ancestor label based on the merge_bases */
+       assert(opt->ancestor == NULL);
+
+       merge_start(opt, result);
+       merge_ort_internal(opt, merge_bases, side1, side2, result);
 }
index 74adccad162b655e2174354eab093217dbd5b124..d53a0a339f338c4fcc0b3965e69319741bd546bf 100644 (file)
@@ -7,7 +7,14 @@ struct commit;
 struct tree;
 
 struct merge_result {
-       /* Whether the merge is clean */
+       /*
+        * Whether the merge is clean; possible values:
+        *    1: clean
+        *    0: not clean (merge conflicts)
+        *   <0: operation aborted prematurely.  (object database
+        *       unreadable, disk full, etc.)  Worktree may be left in an
+        *       inconsistent state if operation failed near the end.
+        */
        int clean;
 
        /*
@@ -27,6 +34,16 @@ struct merge_result {
 /*
  * rename-detecting three-way merge with recursive ancestor consolidation.
  * working tree and index are untouched.
+ *
+ * merge_bases will be consumed (emptied) so make a copy if you need it.
+ *
+ * NOTE: empirically, the recursive algorithm will perform better if you
+ *       pass the merge_bases in the order of oldest commit to the
+ *       newest[1][2].
+ *
+ *       [1] https://lore.kernel.org/git/nycvar.QRO.7.76.6.1907252055500.21907@tvgsbejvaqbjf.bet/
+ *       [2] commit 8918b0c9c2 ("merge-recur: try to merge older merge bases
+ *           first", 2006-08-09)
  */
 void merge_incore_recursive(struct merge_options *opt,
                            struct commit_list *merge_bases,
index f736a0f63234fee610b42abdf2d7458d932e1d70..b052974f191cd82741475a019076c90e45d6d4de 100644 (file)
@@ -3517,17 +3517,6 @@ static int merge_trees_internal(struct merge_options *opt,
        return clean;
 }
 
-static struct commit_list *reverse_commit_list(struct commit_list *list)
-{
-       struct commit_list *next = NULL, *current, *backup;
-       for (current = list; current; current = backup) {
-               backup = current->next;
-               current->next = next;
-               next = current;
-       }
-       return next;
-}
-
 /*
  * Merge the commits h1 and h2, returning a flag (int) indicating the
  * cleanness of the merge.  Also, if opt->priv->call_depth, create a
index 5e998bdaa7998817a4dc73e5d6da711e0615b992..cc5ead999000fea987790b8870a6a597ac6c3de5 100644 (file)
@@ -12,6 +12,7 @@
 #include "sha1-lookup.h"
 #include "pack-objects.h"
 #include "commit-reach.h"
+#include "prio-queue.h"
 
 struct bitmapped_commit {
        struct commit *commit;
@@ -29,7 +30,6 @@ struct bitmap_writer {
        struct ewah_bitmap *tags;
 
        kh_oid_map_t *bitmaps;
-       kh_oid_map_t *reused;
        struct packing_data *to_pack;
 
        struct bitmapped_commit *selected;
@@ -110,10 +110,8 @@ void bitmap_writer_build_type_index(struct packing_data *to_pack,
 /**
  * Compute the actual bitmaps
  */
-static struct object **seen_objects;
-static unsigned int seen_objects_nr, seen_objects_alloc;
 
-static inline void push_bitmapped_commit(struct commit *commit, struct ewah_bitmap *reused)
+static inline void push_bitmapped_commit(struct commit *commit)
 {
        if (writer.selected_nr >= writer.selected_alloc) {
                writer.selected_alloc = (writer.selected_alloc + 32) * 2;
@@ -121,27 +119,12 @@ static inline void push_bitmapped_commit(struct commit *commit, struct ewah_bitm
        }
 
        writer.selected[writer.selected_nr].commit = commit;
-       writer.selected[writer.selected_nr].bitmap = reused;
+       writer.selected[writer.selected_nr].bitmap = NULL;
        writer.selected[writer.selected_nr].flags = 0;
 
        writer.selected_nr++;
 }
 
-static inline void mark_as_seen(struct object *object)
-{
-       ALLOC_GROW(seen_objects, seen_objects_nr + 1, seen_objects_alloc);
-       seen_objects[seen_objects_nr++] = object;
-}
-
-static inline void reset_all_seen(void)
-{
-       unsigned int i;
-       for (i = 0; i < seen_objects_nr; ++i) {
-               seen_objects[i]->flags &= ~(SEEN | ADDED | SHOWN);
-       }
-       seen_objects_nr = 0;
-}
-
 static uint32_t find_object_pos(const struct object_id *oid)
 {
        struct object_entry *entry = packlist_find(writer.to_pack, oid);
@@ -154,60 +137,6 @@ static uint32_t find_object_pos(const struct object_id *oid)
        return oe_in_pack_pos(writer.to_pack, entry);
 }
 
-static void show_object(struct object *object, const char *name, void *data)
-{
-       struct bitmap *base = data;
-       bitmap_set(base, find_object_pos(&object->oid));
-       mark_as_seen(object);
-}
-
-static void show_commit(struct commit *commit, void *data)
-{
-       mark_as_seen((struct object *)commit);
-}
-
-static int
-add_to_include_set(struct bitmap *base, struct commit *commit)
-{
-       khiter_t hash_pos;
-       uint32_t bitmap_pos = find_object_pos(&commit->object.oid);
-
-       if (bitmap_get(base, bitmap_pos))
-               return 0;
-
-       hash_pos = kh_get_oid_map(writer.bitmaps, commit->object.oid);
-       if (hash_pos < kh_end(writer.bitmaps)) {
-               struct bitmapped_commit *bc = kh_value(writer.bitmaps, hash_pos);
-               bitmap_or_ewah(base, bc->bitmap);
-               return 0;
-       }
-
-       bitmap_set(base, bitmap_pos);
-       return 1;
-}
-
-static int
-should_include(struct commit *commit, void *_data)
-{
-       struct bitmap *base = _data;
-
-       if (!add_to_include_set(base, commit)) {
-               struct commit_list *parent = commit->parents;
-
-               mark_as_seen((struct object *)commit);
-
-               while (parent) {
-                       parent->item->object.flags |= SEEN;
-                       mark_as_seen((struct object *)parent->item);
-                       parent = parent->next;
-               }
-
-               return 0;
-       }
-
-       return 1;
-}
-
 static void compute_xor_offsets(void)
 {
        static const int MAX_XOR_OFFSET_SEARCH = 10;
@@ -248,79 +177,326 @@ static void compute_xor_offsets(void)
        }
 }
 
-void bitmap_writer_build(struct packing_data *to_pack)
-{
-       static const double REUSE_BITMAP_THRESHOLD = 0.2;
+struct bb_commit {
+       struct commit_list *reverse_edges;
+       struct bitmap *commit_mask;
+       struct bitmap *bitmap;
+       unsigned selected:1,
+                maximal:1;
+       unsigned idx; /* within selected array */
+};
 
-       int i, reuse_after, need_reset;
-       struct bitmap *base = bitmap_new();
-       struct rev_info revs;
+define_commit_slab(bb_data, struct bb_commit);
 
-       writer.bitmaps = kh_init_oid_map();
-       writer.to_pack = to_pack;
+struct bitmap_builder {
+       struct bb_data data;
+       struct commit **commits;
+       size_t commits_nr, commits_alloc;
+};
 
-       if (writer.show_progress)
-               writer.progress = start_progress("Building bitmaps", writer.selected_nr);
+static void bitmap_builder_init(struct bitmap_builder *bb,
+                               struct bitmap_writer *writer,
+                               struct bitmap_index *old_bitmap)
+{
+       struct rev_info revs;
+       struct commit *commit;
+       struct commit_list *reusable = NULL;
+       struct commit_list *r;
+       unsigned int i, num_maximal = 0;
 
-       repo_init_revisions(to_pack->repo, &revs, NULL);
-       revs.tag_objects = 1;
-       revs.tree_objects = 1;
-       revs.blob_objects = 1;
-       revs.no_walk = 0;
+       memset(bb, 0, sizeof(*bb));
+       init_bb_data(&bb->data);
 
-       revs.include_check = should_include;
        reset_revision_walk();
+       repo_init_revisions(writer->to_pack->repo, &revs, NULL);
+       revs.topo_order = 1;
+       revs.first_parent_only = 1;
+
+       for (i = 0; i < writer->selected_nr; i++) {
+               struct commit *c = writer->selected[i].commit;
+               struct bb_commit *ent = bb_data_at(&bb->data, c);
+
+               ent->selected = 1;
+               ent->maximal = 1;
+               ent->idx = i;
+
+               ent->commit_mask = bitmap_new();
+               bitmap_set(ent->commit_mask, i);
+
+               add_pending_object(&revs, &c->object, "");
+       }
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+
+       while ((commit = get_revision(&revs))) {
+               struct commit_list *p = commit->parents;
+               struct bb_commit *c_ent;
+
+               parse_commit_or_die(commit);
+
+               c_ent = bb_data_at(&bb->data, commit);
+
+               /*
+                * If there is no commit_mask, there is no reason to iterate
+                * over this commit; it is not selected (if it were, it would
+                * not have a blank commit mask) and all its children have
+                * existing bitmaps (see the comment starting with "This commit
+                * has an existing bitmap" below), so it does not contribute
+                * anything to the final bitmap file or its descendants.
+                */
+               if (!c_ent->commit_mask)
+                       continue;
+
+               if (old_bitmap && bitmap_for_commit(old_bitmap, commit)) {
+                       /*
+                        * This commit has an existing bitmap, so we can
+                        * get its bits immediately without an object
+                        * walk. That is, it is reusable as-is and there is no
+                        * need to continue walking beyond it.
+                        *
+                        * Mark it as such and add it to bb->commits separately
+                        * to avoid allocating a position in the commit mask.
+                        */
+                       commit_list_insert(commit, &reusable);
+                       goto next;
+               }
+
+               if (c_ent->maximal) {
+                       num_maximal++;
+                       ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc);
+                       bb->commits[bb->commits_nr++] = commit;
+               }
+
+               if (p) {
+                       struct bb_commit *p_ent = bb_data_at(&bb->data, p->item);
+                       int c_not_p, p_not_c;
+
+                       if (!p_ent->commit_mask) {
+                               p_ent->commit_mask = bitmap_new();
+                               c_not_p = 1;
+                               p_not_c = 0;
+                       } else {
+                               c_not_p = bitmap_is_subset(c_ent->commit_mask, p_ent->commit_mask);
+                               p_not_c = bitmap_is_subset(p_ent->commit_mask, c_ent->commit_mask);
+                       }
+
+                       if (!c_not_p)
+                               continue;
+
+                       bitmap_or(p_ent->commit_mask, c_ent->commit_mask);
+
+                       if (p_not_c)
+                               p_ent->maximal = 1;
+                       else {
+                               p_ent->maximal = 0;
+                               free_commit_list(p_ent->reverse_edges);
+                               p_ent->reverse_edges = NULL;
+                       }
 
-       reuse_after = writer.selected_nr * REUSE_BITMAP_THRESHOLD;
-       need_reset = 0;
+                       if (c_ent->maximal) {
+                               commit_list_insert(commit, &p_ent->reverse_edges);
+                       } else {
+                               struct commit_list *cc = c_ent->reverse_edges;
+
+                               for (; cc; cc = cc->next) {
+                                       if (!commit_list_contains(cc->item, p_ent->reverse_edges))
+                                               commit_list_insert(cc->item, &p_ent->reverse_edges);
+                               }
+                       }
+               }
+
+next:
+               bitmap_free(c_ent->commit_mask);
+               c_ent->commit_mask = NULL;
+       }
+
+       for (r = reusable; r; r = r->next) {
+               ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc);
+               bb->commits[bb->commits_nr++] = r->item;
+       }
+
+       trace2_data_intmax("pack-bitmap-write", the_repository,
+                          "num_selected_commits", writer->selected_nr);
+       trace2_data_intmax("pack-bitmap-write", the_repository,
+                          "num_maximal_commits", num_maximal);
+
+       free_commit_list(reusable);
+}
+
+static void bitmap_builder_clear(struct bitmap_builder *bb)
+{
+       clear_bb_data(&bb->data);
+       free(bb->commits);
+       bb->commits_nr = bb->commits_alloc = 0;
+}
+
+static void fill_bitmap_tree(struct bitmap *bitmap,
+                            struct tree *tree)
+{
+       uint32_t pos;
+       struct tree_desc desc;
+       struct name_entry entry;
+
+       /*
+        * If our bit is already set, then there is nothing to do. Both this
+        * tree and all of its children will be set.
+        */
+       pos = find_object_pos(&tree->object.oid);
+       if (bitmap_get(bitmap, pos))
+               return;
+       bitmap_set(bitmap, pos);
 
-       for (i = writer.selected_nr - 1; i >= 0; --i) {
-               struct bitmapped_commit *stored;
-               struct object *object;
+       if (parse_tree(tree) < 0)
+               die("unable to load tree object %s",
+                   oid_to_hex(&tree->object.oid));
+       init_tree_desc(&desc, tree->buffer, tree->size);
 
-               khiter_t hash_pos;
-               int hash_ret;
+       while (tree_entry(&desc, &entry)) {
+               switch (object_type(entry.mode)) {
+               case OBJ_TREE:
+                       fill_bitmap_tree(bitmap,
+                                        lookup_tree(the_repository, &entry.oid));
+                       break;
+               case OBJ_BLOB:
+                       bitmap_set(bitmap, find_object_pos(&entry.oid));
+                       break;
+               default:
+                       /* Gitlink, etc; not reachable */
+                       break;
+               }
+       }
 
-               stored = &writer.selected[i];
-               object = (struct object *)stored->commit;
+       free_tree_buffer(tree);
+}
 
-               if (stored->bitmap == NULL) {
-                       if (i < writer.selected_nr - 1 &&
-                           (need_reset ||
-                            !in_merge_bases(writer.selected[i + 1].commit,
-                                            stored->commit))) {
-                           bitmap_reset(base);
-                           reset_all_seen();
+static void fill_bitmap_commit(struct bb_commit *ent,
+                              struct commit *commit,
+                              struct prio_queue *queue,
+                              struct prio_queue *tree_queue,
+                              struct bitmap_index *old_bitmap,
+                              const uint32_t *mapping)
+{
+       if (!ent->bitmap)
+               ent->bitmap = bitmap_new();
+
+       prio_queue_put(queue, commit);
+
+       while (queue->nr) {
+               struct commit_list *p;
+               struct commit *c = prio_queue_get(queue);
+
+               if (old_bitmap && mapping) {
+                       struct ewah_bitmap *old = bitmap_for_commit(old_bitmap, c);
+                       /*
+                        * If this commit has an old bitmap, then translate that
+                        * bitmap and add its bits to this one. No need to walk
+                        * parents or the tree for this commit.
+                        */
+                       if (old && !rebuild_bitmap(mapping, old, ent->bitmap))
+                               continue;
+               }
+
+               /*
+                * Mark ourselves and queue our tree. The commit
+                * walk ensures we cover all parents.
+                */
+               bitmap_set(ent->bitmap, find_object_pos(&c->object.oid));
+               prio_queue_put(tree_queue, get_commit_tree(c));
+
+               for (p = c->parents; p; p = p->next) {
+                       int pos = find_object_pos(&p->item->object.oid);
+                       if (!bitmap_get(ent->bitmap, pos)) {
+                               bitmap_set(ent->bitmap, pos);
+                               prio_queue_put(queue, p->item);
                        }
+               }
+       }
 
-                       add_pending_object(&revs, object, "");
-                       revs.include_check_data = base;
+       while (tree_queue->nr)
+               fill_bitmap_tree(ent->bitmap, prio_queue_get(tree_queue));
+}
 
-                       if (prepare_revision_walk(&revs))
-                               die("revision walk setup failed");
+static void store_selected(struct bb_commit *ent, struct commit *commit)
+{
+       struct bitmapped_commit *stored = &writer.selected[ent->idx];
+       khiter_t hash_pos;
+       int hash_ret;
 
-                       traverse_commit_list(&revs, show_commit, show_object, base);
+       stored->bitmap = bitmap_to_ewah(ent->bitmap);
 
-                       object_array_clear(&revs.pending);
+       hash_pos = kh_put_oid_map(writer.bitmaps, commit->object.oid, &hash_ret);
+       if (hash_ret == 0)
+               die("Duplicate entry when writing index: %s",
+                   oid_to_hex(&commit->object.oid));
+       kh_value(writer.bitmaps, hash_pos) = stored;
+}
 
-                       stored->bitmap = bitmap_to_ewah(base);
-                       need_reset = 0;
-               } else
-                       need_reset = 1;
+void bitmap_writer_build(struct packing_data *to_pack)
+{
+       struct bitmap_builder bb;
+       size_t i;
+       int nr_stored = 0; /* for progress */
+       struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
+       struct prio_queue tree_queue = { NULL };
+       struct bitmap_index *old_bitmap;
+       uint32_t *mapping;
 
-               if (i >= reuse_after)
-                       stored->flags |= BITMAP_FLAG_REUSE;
+       writer.bitmaps = kh_init_oid_map();
+       writer.to_pack = to_pack;
 
-               hash_pos = kh_put_oid_map(writer.bitmaps, object->oid, &hash_ret);
-               if (hash_ret == 0)
-                       die("Duplicate entry when writing index: %s",
-                           oid_to_hex(&object->oid));
+       if (writer.show_progress)
+               writer.progress = start_progress("Building bitmaps", writer.selected_nr);
+       trace2_region_enter("pack-bitmap-write", "building_bitmaps_total",
+                           the_repository);
+
+       old_bitmap = prepare_bitmap_git(to_pack->repo);
+       if (old_bitmap)
+               mapping = create_bitmap_mapping(old_bitmap, to_pack);
+       else
+               mapping = NULL;
+
+       bitmap_builder_init(&bb, &writer, old_bitmap);
+       for (i = bb.commits_nr; i > 0; i--) {
+               struct commit *commit = bb.commits[i-1];
+               struct bb_commit *ent = bb_data_at(&bb.data, commit);
+               struct commit *child;
+               int reused = 0;
+
+               fill_bitmap_commit(ent, commit, &queue, &tree_queue,
+                                  old_bitmap, mapping);
+
+               if (ent->selected) {
+                       store_selected(ent, commit);
+                       nr_stored++;
+                       display_progress(writer.progress, nr_stored);
+               }
 
-               kh_value(writer.bitmaps, hash_pos) = stored;
-               display_progress(writer.progress, writer.selected_nr - i);
+               while ((child = pop_commit(&ent->reverse_edges))) {
+                       struct bb_commit *child_ent =
+                               bb_data_at(&bb.data, child);
+
+                       if (child_ent->bitmap)
+                               bitmap_or(child_ent->bitmap, ent->bitmap);
+                       else if (reused)
+                               child_ent->bitmap = bitmap_dup(ent->bitmap);
+                       else {
+                               child_ent->bitmap = ent->bitmap;
+                               reused = 1;
+                       }
+               }
+               if (!reused)
+                       bitmap_free(ent->bitmap);
+               ent->bitmap = NULL;
        }
+       clear_prio_queue(&queue);
+       clear_prio_queue(&tree_queue);
+       bitmap_builder_clear(&bb);
+       free(mapping);
+
+       trace2_region_leave("pack-bitmap-write", "building_bitmaps_total",
+                           the_repository);
 
-       bitmap_free(base);
        stop_progress(&writer.progress);
 
        compute_xor_offsets();
@@ -360,35 +536,6 @@ static int date_compare(const void *_a, const void *_b)
        return (long)b->date - (long)a->date;
 }
 
-void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack)
-{
-       struct bitmap_index *bitmap_git;
-       if (!(bitmap_git = prepare_bitmap_git(to_pack->repo)))
-               return;
-
-       writer.reused = kh_init_oid_map();
-       rebuild_existing_bitmaps(bitmap_git, to_pack, writer.reused,
-                                writer.show_progress);
-       /*
-        * NEEDSWORK: rebuild_existing_bitmaps() makes writer.reused reference
-        * some bitmaps in bitmap_git, so we can't free the latter.
-        */
-}
-
-static struct ewah_bitmap *find_reused_bitmap(const struct object_id *oid)
-{
-       khiter_t hash_pos;
-
-       if (!writer.reused)
-               return NULL;
-
-       hash_pos = kh_get_oid_map(writer.reused, *oid);
-       if (hash_pos >= kh_end(writer.reused))
-               return NULL;
-
-       return kh_value(writer.reused, hash_pos);
-}
-
 void bitmap_writer_select_commits(struct commit **indexed_commits,
                                  unsigned int indexed_commits_nr,
                                  int max_bitmaps)
@@ -402,12 +549,11 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
 
        if (indexed_commits_nr < 100) {
                for (i = 0; i < indexed_commits_nr; ++i)
-                       push_bitmapped_commit(indexed_commits[i], NULL);
+                       push_bitmapped_commit(indexed_commits[i]);
                return;
        }
 
        for (;;) {
-               struct ewah_bitmap *reused_bitmap = NULL;
                struct commit *chosen = NULL;
 
                next = next_commit_index(i);
@@ -422,15 +568,13 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
 
                if (next == 0) {
                        chosen = indexed_commits[i];
-                       reused_bitmap = find_reused_bitmap(&chosen->object.oid);
                } else {
                        chosen = indexed_commits[i + next];
 
                        for (j = 0; j <= next; ++j) {
                                struct commit *cm = indexed_commits[i + j];
 
-                               reused_bitmap = find_reused_bitmap(&cm->object.oid);
-                               if (reused_bitmap || (cm->object.flags & NEEDS_BITMAP) != 0) {
+                               if ((cm->object.flags & NEEDS_BITMAP) != 0) {
                                        chosen = cm;
                                        break;
                                }
@@ -440,7 +584,7 @@ void bitmap_writer_select_commits(struct commit **indexed_commits,
                        }
                }
 
-               push_bitmapped_commit(chosen, reused_bitmap);
+               push_bitmapped_commit(chosen);
 
                i += next + 1;
                display_progress(writer.progress, i);
index 4077e731e800c52548243452e9a5cf715a8f43b0..d88745fb026adeb41a9d828c57314af0a6a14283 100644 (file)
@@ -138,9 +138,10 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index)
 static int load_bitmap_header(struct bitmap_index *index)
 {
        struct bitmap_disk_header *header = (void *)index->map;
+       size_t header_size = sizeof(*header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz;
 
-       if (index->map_size < sizeof(*header) + the_hash_algo->rawsz)
-               return error("Corrupted bitmap index (missing header data)");
+       if (index->map_size < header_size + the_hash_algo->rawsz)
+               return error("Corrupted bitmap index (too small)");
 
        if (memcmp(header->magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE)) != 0)
                return error("Corrupted bitmap index file (wrong header)");
@@ -152,19 +153,23 @@ static int load_bitmap_header(struct bitmap_index *index)
        /* Parse known bitmap format options */
        {
                uint32_t flags = ntohs(header->options);
+               size_t cache_size = st_mult(index->pack->num_objects, sizeof(uint32_t));
+               unsigned char *index_end = index->map + index->map_size - the_hash_algo->rawsz;
 
                if ((flags & BITMAP_OPT_FULL_DAG) == 0)
                        return error("Unsupported options for bitmap index file "
                                "(Git requires BITMAP_OPT_FULL_DAG)");
 
                if (flags & BITMAP_OPT_HASH_CACHE) {
-                       unsigned char *end = index->map + index->map_size - the_hash_algo->rawsz;
-                       index->hashes = ((uint32_t *)end) - index->pack->num_objects;
+                       if (cache_size > index_end - index->map - header_size)
+                               return error("corrupted bitmap index file (too short to fit hash cache)");
+                       index->hashes = (void *)(index_end - cache_size);
+                       index_end -= cache_size;
                }
        }
 
        index->entry_count = ntohl(header->entry_count);
-       index->map_pos += sizeof(*header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz;
+       index->map_pos += header_size;
        return 0;
 }
 
@@ -224,11 +229,16 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
                uint32_t commit_idx_pos;
                struct object_id oid;
 
+               if (index->map_size - index->map_pos < 6)
+                       return error("corrupt ewah bitmap: truncated header for entry %d", i);
+
                commit_idx_pos = read_be32(index->map, &index->map_pos);
                xor_offset = read_u8(index->map, &index->map_pos);
                flags = read_u8(index->map, &index->map_pos);
 
-               nth_packed_object_id(&oid, index->pack, commit_idx_pos);
+               if (nth_packed_object_id(&oid, index->pack, commit_idx_pos) < 0)
+                       return error("corrupt ewah bitmap: commit index %u out of range",
+                                    (unsigned)commit_idx_pos);
 
                bitmap = read_bitmap_1(index);
                if (!bitmap)
@@ -370,6 +380,16 @@ struct include_data {
        struct bitmap *seen;
 };
 
+struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
+                                     struct commit *commit)
+{
+       khiter_t hash_pos = kh_get_oid_map(bitmap_git->bitmaps,
+                                          commit->object.oid);
+       if (hash_pos >= kh_end(bitmap_git->bitmaps))
+               return NULL;
+       return lookup_stored_bitmap(kh_value(bitmap_git->bitmaps, hash_pos));
+}
+
 static inline int bitmap_position_extended(struct bitmap_index *bitmap_git,
                                           const struct object_id *oid)
 {
@@ -455,10 +475,10 @@ static void show_commit(struct commit *commit, void *data)
 
 static int add_to_include_set(struct bitmap_index *bitmap_git,
                              struct include_data *data,
-                             const struct object_id *oid,
+                             struct commit *commit,
                              int bitmap_pos)
 {
-       khiter_t hash_pos;
+       struct ewah_bitmap *partial;
 
        if (data->seen && bitmap_get(data->seen, bitmap_pos))
                return 0;
@@ -466,10 +486,9 @@ static int add_to_include_set(struct bitmap_index *bitmap_git,
        if (bitmap_get(data->base, bitmap_pos))
                return 0;
 
-       hash_pos = kh_get_oid_map(bitmap_git->bitmaps, *oid);
-       if (hash_pos < kh_end(bitmap_git->bitmaps)) {
-               struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, hash_pos);
-               bitmap_or_ewah(data->base, lookup_stored_bitmap(st));
+       partial = bitmap_for_commit(bitmap_git, commit);
+       if (partial) {
+               bitmap_or_ewah(data->base, partial);
                return 0;
        }
 
@@ -488,8 +507,7 @@ static int should_include(struct commit *commit, void *_data)
                                                  (struct object *)commit,
                                                  NULL);
 
-       if (!add_to_include_set(data->bitmap_git, data, &commit->object.oid,
-                               bitmap_pos)) {
+       if (!add_to_include_set(data->bitmap_git, data, commit, bitmap_pos)) {
                struct commit_list *parent = commit->parents;
 
                while (parent) {
@@ -503,6 +521,23 @@ static int should_include(struct commit *commit, void *_data)
        return 1;
 }
 
+static int add_commit_to_bitmap(struct bitmap_index *bitmap_git,
+                               struct bitmap **base,
+                               struct commit *commit)
+{
+       struct ewah_bitmap *or_with = bitmap_for_commit(bitmap_git, commit);
+
+       if (!or_with)
+               return 0;
+
+       if (*base == NULL)
+               *base = ewah_to_bitmap(or_with);
+       else
+               bitmap_or_ewah(*base, or_with);
+
+       return 1;
+}
+
 static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
                                   struct rev_info *revs,
                                   struct object_list *roots,
@@ -526,21 +561,10 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
                struct object *object = roots->item;
                roots = roots->next;
 
-               if (object->type == OBJ_COMMIT) {
-                       khiter_t pos = kh_get_oid_map(bitmap_git->bitmaps, object->oid);
-
-                       if (pos < kh_end(bitmap_git->bitmaps)) {
-                               struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, pos);
-                               struct ewah_bitmap *or_with = lookup_stored_bitmap(st);
-
-                               if (base == NULL)
-                                       base = ewah_to_bitmap(or_with);
-                               else
-                                       bitmap_or_ewah(base, or_with);
-
-                               object->flags |= SEEN;
-                               continue;
-                       }
+               if (object->type == OBJ_COMMIT &&
+                   add_commit_to_bitmap(bitmap_git, &base, (struct commit *)object)) {
+                       object->flags |= SEEN;
+                       continue;
                }
 
                object_list_insert(object, &not_mapped);
@@ -1272,10 +1296,10 @@ void test_bitmap_walk(struct rev_info *revs)
 {
        struct object *root;
        struct bitmap *result = NULL;
-       khiter_t pos;
        size_t result_popcnt;
        struct bitmap_test_data tdata;
        struct bitmap_index *bitmap_git;
+       struct ewah_bitmap *bm;
 
        if (!(bitmap_git = prepare_bitmap_git(revs->repo)))
                die("failed to load bitmap indexes");
@@ -1287,12 +1311,9 @@ void test_bitmap_walk(struct rev_info *revs)
                bitmap_git->version, bitmap_git->entry_count);
 
        root = revs->pending.objects[0].item;
-       pos = kh_get_oid_map(bitmap_git->bitmaps, root->oid);
-
-       if (pos < kh_end(bitmap_git->bitmaps)) {
-               struct stored_bitmap *st = kh_value(bitmap_git->bitmaps, pos);
-               struct ewah_bitmap *bm = lookup_stored_bitmap(st);
+       bm = bitmap_for_commit(bitmap_git, (struct commit *)root);
 
+       if (bm) {
                fprintf(stderr, "Found bitmap for %s. %d bits / %08x checksum\n",
                        oid_to_hex(&root->oid), (int)bm->bit_size, ewah_checksum(bm));
 
@@ -1323,14 +1344,14 @@ void test_bitmap_walk(struct rev_info *revs)
        if (bitmap_equals(result, tdata.base))
                fprintf(stderr, "OK!\n");
        else
-               fprintf(stderr, "Mismatch!\n");
+               die("mismatch in bitmap results");
 
        free_bitmap_index(bitmap_git);
 }
 
-static int rebuild_bitmap(uint32_t *reposition,
-                         struct ewah_bitmap *source,
-                         struct bitmap *dest)
+int rebuild_bitmap(const uint32_t *reposition,
+                  struct ewah_bitmap *source,
+                  struct bitmap *dest)
 {
        uint32_t pos = 0;
        struct ewah_iterator it;
@@ -1359,19 +1380,11 @@ static int rebuild_bitmap(uint32_t *reposition,
        return 0;
 }
 
-int rebuild_existing_bitmaps(struct bitmap_index *bitmap_git,
-                            struct packing_data *mapping,
-                            kh_oid_map_t *reused_bitmaps,
-                            int show_progress)
+uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
+                               struct packing_data *mapping)
 {
        uint32_t i, num_objects;
        uint32_t *reposition;
-       struct bitmap *rebuild;
-       struct stored_bitmap *stored;
-       struct progress *progress = NULL;
-
-       khiter_t hash_pos;
-       int hash_ret;
 
        num_objects = bitmap_git->pack->num_objects;
        reposition = xcalloc(num_objects, sizeof(uint32_t));
@@ -1389,33 +1402,7 @@ int rebuild_existing_bitmaps(struct bitmap_index *bitmap_git,
                        reposition[i] = oe_in_pack_pos(mapping, oe) + 1;
        }
 
-       rebuild = bitmap_new();
-       i = 0;
-
-       if (show_progress)
-               progress = start_progress("Reusing bitmaps", 0);
-
-       kh_foreach_value(bitmap_git->bitmaps, stored, {
-               if (stored->flags & BITMAP_FLAG_REUSE) {
-                       if (!rebuild_bitmap(reposition,
-                                           lookup_stored_bitmap(stored),
-                                           rebuild)) {
-                               hash_pos = kh_put_oid_map(reused_bitmaps,
-                                                         stored->oid,
-                                                         &hash_ret);
-                               kh_value(reused_bitmaps, hash_pos) =
-                                       bitmap_to_ewah(rebuild);
-                       }
-                       bitmap_reset(rebuild);
-                       display_progress(progress, ++i);
-               }
-       });
-
-       stop_progress(&progress);
-
-       free(reposition);
-       bitmap_free(rebuild);
-       return 0;
+       return reposition;
 }
 
 void free_bitmap_index(struct bitmap_index *b)
index 1203120c4321c15d488e623385d0ce9633b66477..25dfcf56156bf8b9b03ebbbaae895a71fc8cf89b 100644 (file)
@@ -73,7 +73,13 @@ void bitmap_writer_set_checksum(unsigned char *sha1);
 void bitmap_writer_build_type_index(struct packing_data *to_pack,
                                    struct pack_idx_entry **index,
                                    uint32_t index_nr);
-void bitmap_writer_reuse_bitmaps(struct packing_data *to_pack);
+uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git,
+                               struct packing_data *mapping);
+int rebuild_bitmap(const uint32_t *reposition,
+                  struct ewah_bitmap *source,
+                  struct bitmap *dest);
+struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git,
+                                     struct commit *commit);
 void bitmap_writer_select_commits(struct commit **indexed_commits,
                unsigned int indexed_commits_nr, int max_bitmaps);
 void bitmap_writer_build(struct packing_data *to_pack);
index 7a7708a0ea707ac58f1961a65b40fa89ea8631d3..05eef7fda0b6e3f003e9df7e261baba0590ed00c 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1418,6 +1418,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
                struct string_list filter_list = STRING_LIST_INIT_NODUP;
                struct strbuf sepbuf = STRBUF_INIT;
+               struct strbuf kvsepbuf = STRBUF_INIT;
                size_t ret = 0;
 
                opts.no_divider = 1;
@@ -1449,8 +1450,17 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
                                        strbuf_expand(&sepbuf, fmt, strbuf_expand_literal_cb, NULL);
                                        free(fmt);
                                        opts.separator = &sepbuf;
+                               } else if (match_placeholder_arg_value(arg, "key_value_separator", &arg, &argval, &arglen)) {
+                                       char *fmt;
+
+                                       strbuf_reset(&kvsepbuf);
+                                       fmt = xstrndup(argval, arglen);
+                                       strbuf_expand(&kvsepbuf, fmt, strbuf_expand_literal_cb, NULL);
+                                       free(fmt);
+                                       opts.key_value_separator = &kvsepbuf;
                                } else if (!match_placeholder_bool_arg(arg, "only", &arg, &opts.only_trailers) &&
                                           !match_placeholder_bool_arg(arg, "unfold", &arg, &opts.unfold) &&
+                                          !match_placeholder_bool_arg(arg, "keyonly", &arg, &opts.key_only) &&
                                           !match_placeholder_bool_arg(arg, "valueonly", &arg, &opts.value_only))
                                        break;
                        }
index b3bb59f06644739c859adb22836e2761da2a91be..b561445329204f20a326cf7f7a5b897ca28d0934 100644 (file)
@@ -1477,6 +1477,7 @@ static int get_next_submodule(struct child_process *cp,
                        strbuf_release(&submodule_prefix);
                        return 1;
                } else {
+                       struct strbuf empty_submodule_path = STRBUF_INIT;
 
                        fetch_task_release(task);
                        free(task);
@@ -1485,13 +1486,17 @@ static int get_next_submodule(struct child_process *cp,
                         * An empty directory is normal,
                         * the submodule is not initialized
                         */
+                       strbuf_addf(&empty_submodule_path, "%s/%s/",
+                                                       spf->r->worktree,
+                                                       ce->name);
                        if (S_ISGITLINK(ce->ce_mode) &&
-                           !is_empty_dir(ce->name)) {
+                           !is_empty_dir(empty_submodule_path.buf)) {
                                spf->result = 1;
                                strbuf_addf(err,
                                            _("Could not access submodule '%s'\n"),
                                            ce->name);
                        }
+                       strbuf_release(&empty_submodule_path);
                }
        }
 
index 22d727cef83cea44be590e0266b01d6dd9f96041..e385c6896fcf95b35ac3ed6ea6287fa4b6c4d1cd 100644 (file)
@@ -147,10 +147,7 @@ test_run_perf_ () {
        "$GTIME" -f "%E %U %S" -o test_time.$i "$SHELL" -c '
 . '"$TEST_DIRECTORY"/test-lib-functions.sh'
 test_export () {
-       [ $# != 0 ] || return 0
-       test_export_="$test_export_ $1"
-       shift
-       test_export "$@"
+       test_export_="$test_export_ $*"
 }
 '"$1"'
 ret=$?
index 408b97d5af9551f91a86e70c6c37b912719bfd15..51d7d40ec1c65e25706dd015adda6f9566415653 100755 (executable)
@@ -3,6 +3,16 @@
 test_description='test git rev-parse'
 . ./test-lib.sh
 
+test_one () {
+       dir="$1" &&
+       expect="$2" &&
+       shift &&
+       shift &&
+       echo "$expect" >expect &&
+       git -C "$dir" rev-parse "$@" >actual &&
+       test_cmp expect actual
+}
+
 # usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir
 test_rev_parse () {
        d=
@@ -60,7 +70,13 @@ ROOT=$(pwd)
 
 test_expect_success 'setup' '
        mkdir -p sub/dir work &&
-       cp -R .git repo.git
+       cp -R .git repo.git &&
+       git checkout -B main &&
+       test_commit abc &&
+       git checkout -b side &&
+       test_commit def &&
+       git checkout main &&
+       git worktree add worktree side
 '
 
 test_rev_parse toplevel false false true '' .git "$ROOT/.git"
@@ -88,6 +104,45 @@ test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = tru
 
 test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
 
+test_expect_success 'rev-parse --path-format=absolute' '
+       test_one "." "$ROOT/.git" --path-format=absolute --git-dir &&
+       test_one "." "$ROOT/.git" --path-format=absolute --git-common-dir &&
+       test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-dir &&
+       test_one "sub/dir" "$ROOT/.git" --path-format=absolute --git-common-dir &&
+       test_one "worktree" "$ROOT/.git/worktrees/worktree" --path-format=absolute --git-dir &&
+       test_one "worktree" "$ROOT/.git" --path-format=absolute --git-common-dir &&
+       test_one "." "$ROOT" --path-format=absolute --show-toplevel &&
+       test_one "." "$ROOT/.git/objects" --path-format=absolute --git-path objects &&
+       test_one "." "$ROOT/.git/objects/foo/bar/baz" --path-format=absolute --git-path objects/foo/bar/baz
+'
+
+test_expect_success 'rev-parse --path-format=relative' '
+       test_one "." ".git" --path-format=relative --git-dir &&
+       test_one "." ".git" --path-format=relative --git-common-dir &&
+       test_one "sub/dir" "../../.git" --path-format=relative --git-dir &&
+       test_one "sub/dir" "../../.git" --path-format=relative --git-common-dir &&
+       test_one "worktree" "../.git/worktrees/worktree" --path-format=relative --git-dir &&
+       test_one "worktree" "../.git" --path-format=relative --git-common-dir &&
+       test_one "." "./" --path-format=relative --show-toplevel &&
+       test_one "." ".git/objects" --path-format=relative --git-path objects &&
+       test_one "." ".git/objects/foo/bar/baz" --path-format=relative --git-path objects/foo/bar/baz
+'
+
+test_expect_success '--path-format=relative does not affect --absolute-git-dir' '
+       git rev-parse --path-format=relative --absolute-git-dir >actual &&
+       echo "$ROOT/.git" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '--path-format can change in the middle of the command line' '
+       git rev-parse --path-format=absolute --git-dir --path-format=relative --git-path objects/foo/bar >actual &&
+       cat >expect <<-EOF &&
+       $ROOT/.git
+       .git/objects/foo/bar
+       EOF
+       test_cmp expect actual
+'
+
 test_expect_success 'git-common-dir from worktree root' '
        echo .git >expect &&
        git rev-parse --git-common-dir >actual &&
index 1fe468bfe8bc4f9571ea1ce588c8886610a9744b..f73741886b6d60e9fb583ce9e1b2606dbb00b31a 100755 (executable)
@@ -104,6 +104,16 @@ test_expect_success 'repo not found; .git not file' '
        test_i18ngrep ".git is not a file" err
 '
 
+test_expect_success 'repo not found; .git not referencing repo' '
+       test_when_finished "rm -rf side not-a-repo && git worktree prune" &&
+       git worktree add --detach side &&
+       sed s,\.git/worktrees/side$,not-a-repo, side/.git >side/.newgit &&
+       mv side/.newgit side/.git &&
+       mkdir not-a-repo &&
+       test_must_fail git worktree repair side 2>err &&
+       test_i18ngrep ".git file does not reference a repository" 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 &&
@@ -176,4 +186,20 @@ test_expect_success 'repair multiple gitdir files' '
        test_must_be_empty err
 '
 
+test_expect_success 'repair moved main and linked worktrees' '
+       test_when_finished "rm -rf main side mainmoved sidemoved" &&
+       test_create_repo main &&
+       test_commit -C main init &&
+       git -C main worktree add --detach ../side &&
+       sed "s,side/\.git$,sidemoved/.git," \
+               main/.git/worktrees/side/gitdir >expect-gitdir &&
+       sed "s,main/.git/worktrees/side$,mainmoved/.git/worktrees/side," \
+               side/.git >expect-gitfile &&
+       mv main mainmoved &&
+       mv side sidemoved &&
+       git -C mainmoved worktree repair ../sidemoved &&
+       test_cmp expect-gitdir mainmoved/.git/worktrees/side/gitdir &&
+       test_cmp expect-gitfile sidemoved/.git
+'
+
 test_done
index 3ec3e1d730339961853e5ab4e6b31a2f33b238df..0af3b85d17276f1861e73b1a8b729b44e505cddf 100755 (executable)
@@ -306,7 +306,9 @@ test_expect_success 'git branch --list -v with --abbrev' '
 
        git branch -v --list --no-abbrev t >actual.noabbrev &&
        git branch -v --list --abbrev=0 t >actual.0abbrev &&
+       git -c core.abbrev=no branch -v --list t >actual.noabbrev-conf &&
        test_cmp actual.noabbrev actual.0abbrev &&
+       test_cmp actual.noabbrev actual.noabbrev-conf &&
 
        git branch -v --list --abbrev=36 t >actual.36abbrev &&
        # how many hexdigits are used?
index 204c149d5a495960d7dd4329f9bef0d3770cf0fa..749bc1431ac26660a477ad33364feba62c426956 100755 (executable)
@@ -605,6 +605,12 @@ test_expect_success 'pretty format %(trailers) shows trailers' '
        test_cmp expect actual
 '
 
+test_expect_success 'pretty format %(trailers:) enables no options' '
+       git log --no-walk --pretty="%(trailers:)" >actual &&
+       # "expect" the same as the test above
+       test_cmp expect actual
+'
+
 test_expect_success '%(trailers:only) shows only "key: value" trailers' '
        git log --no-walk --pretty="%(trailers:only)" >actual &&
        {
@@ -709,19 +715,101 @@ test_expect_success '%(trailers:key) without value is error' '
        test_cmp expect actual
 '
 
+test_expect_success '%(trailers:keyonly) shows only keys' '
+       git log --no-walk --pretty="format:%(trailers:keyonly)" >actual &&
+       test_write_lines \
+               "Signed-off-by" \
+               "Acked-by" \
+               "[ v2 updated patch description ]" \
+               "Signed-off-by" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=foo,keyonly) shows only key' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by,keyonly)" >actual &&
+       echo "Acked-by" >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success '%(trailers:key=foo,valueonly) shows only value' '
        git log --no-walk --pretty="format:%(trailers:key=Acked-by,valueonly)" >actual &&
        echo "A U Thor <author@example.com>" >expect &&
        test_cmp expect actual
 '
 
+test_expect_success '%(trailers:valueonly) shows only values' '
+       git log --no-walk --pretty="format:%(trailers:valueonly)" >actual &&
+       test_write_lines \
+               "A U Thor <author@example.com>" \
+               "A U Thor <author@example.com>" \
+               "[ v2 updated patch description ]" \
+               "A U Thor" \
+               "  <author@example.com>" >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success '%(trailers:key=foo,keyonly,valueonly) shows nothing' '
+       git log --no-walk --pretty="format:%(trailers:key=Acked-by,keyonly,valueonly)" >actual &&
+       echo >expect &&
+       test_cmp expect actual
+'
+
 test_expect_success 'pretty format %(trailers:separator) changes separator' '
+       git log --no-walk --pretty=format:"X%(trailers:separator=%x00)X" >actual &&
+       (
+               printf "XSigned-off-by: A U Thor <author@example.com>\0" &&
+               printf "Acked-by: A U Thor <author@example.com>\0" &&
+               printf "[ v2 updated patch description ]\0" &&
+               printf "Signed-off-by: A U Thor\n  <author@example.com>X"
+       ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:separator=X,unfold) changes separator' '
        git log --no-walk --pretty=format:"X%(trailers:separator=%x00,unfold)X" >actual &&
-       printf "XSigned-off-by: A U Thor <author@example.com>\0Acked-by: A U Thor <author@example.com>\0[ v2 updated patch description ]\0Signed-off-by: A U Thor <author@example.com>X" >expect &&
+       (
+               printf "XSigned-off-by: A U Thor <author@example.com>\0" &&
+               printf "Acked-by: A U Thor <author@example.com>\0" &&
+               printf "[ v2 updated patch description ]\0" &&
+               printf "Signed-off-by: A U Thor <author@example.com>X"
+       ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key_value_separator) changes key-value separator' '
+       git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00)X" >actual &&
+       (
+               printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
+               printf "Acked-by\0A U Thor <author@example.com>\n" &&
+               printf "[ v2 updated patch description ]\n" &&
+               printf "Signed-off-by\0A U Thor\n  <author@example.com>\nX"
+       ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:key_value_separator,unfold) changes key-value separator' '
+       git log --no-walk --pretty=format:"X%(trailers:key_value_separator=%x00,unfold)X" >actual &&
+       (
+               printf "XSigned-off-by\0A U Thor <author@example.com>\n" &&
+               printf "Acked-by\0A U Thor <author@example.com>\n" &&
+               printf "[ v2 updated patch description ]\n" &&
+               printf "Signed-off-by\0A U Thor <author@example.com>\nX"
+       ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty format %(trailers:separator,key_value_separator) changes both separators' '
+       git log --no-walk --pretty=format:"%(trailers:separator=%x00,key_value_separator=%x00%x00,unfold)" >actual &&
+       (
+               printf "Signed-off-by\0\0A U Thor <author@example.com>\0" &&
+               printf "Acked-by\0\0A U Thor <author@example.com>\0" &&
+               printf "[ v2 updated patch description ]\0" &&
+               printf "Signed-off-by\0\0A U Thor <author@example.com>"
+       ) >expect &&
        test_cmp expect actual
 '
 
-test_expect_success 'pretty format %(trailers) combining separator/key/valueonly' '
+test_expect_success 'pretty format %(trailers) combining separator/key/keyonly/valueonly' '
        git commit --allow-empty -F - <<-\EOF &&
        Important fix
 
@@ -748,6 +836,13 @@ test_expect_success 'pretty format %(trailers) combining separator/key/valueonly
                "Does not close any tickets" \
                "Another fix #567, #890" \
                "Important fix #1234" >expect &&
+       test_cmp expect actual &&
+
+       git log --pretty="%s% (trailers:separator=%x2c%x20,key=Closes,keyonly)" HEAD~3.. >actual &&
+       test_write_lines \
+               "Does not close any tickets" \
+               "Another fix Closes, Closes" \
+               "Important fix Closes" >expect &&
        test_cmp expect actual
 '
 
index 1d40fcad3901c2c58b8e562fb596b34785671f5f..3a2c9d2d8e7b4676fcd82dccf4e874da156e778f 100755 (executable)
@@ -20,84 +20,172 @@ has_any () {
        grep -Ff "$1" "$2"
 }
 
+# To ensure the logic for "maximal commits" is exercised, make
+# the repository a bit more complicated.
+#
+#    other                         second
+#      *                             *
+# (99 commits)                  (99 commits)
+#      *                             *
+#      |\                           /|
+#      | * octo-other  octo-second * |
+#      |/|\_________  ____________/|\|
+#      | \          \/  __________/  |
+#      |  | ________/\ /             |
+#      *  |/          * merge-right  *
+#      | _|__________/ \____________ |
+#      |/ |                         \|
+# (l1) *  * merge-left               * (r1)
+#      | / \________________________ |
+#      |/                           \|
+# (l2) *                             * (r2)
+#       \___________________________ |
+#                                   \|
+#                                    * (base)
+#
+# We only push bits down the first-parent history, which
+# makes some of these commits unimportant!
+#
+# The important part for the maximal commit algorithm is how
+# the bitmasks are extended. Assuming starting bit positions
+# for second (bit 0) and other (bit 1), the bitmasks at the
+# end should be:
+#
+#      second: 1       (maximal, selected)
+#       other: 01      (maximal, selected)
+#      (base): 11 (maximal)
+#
+# This complicated history was important for a previous
+# version of the walk that guarantees never walking a
+# commit multiple times. That goal might be important
+# again, so preserve this complicated case. For now, this
+# test will guarantee that the bitmaps are computed
+# correctly, even with the repeat calculations.
+
 test_expect_success 'setup repo with moderate-sized history' '
-       test_commit_bulk --id=file 100 &&
+       test_commit_bulk --id=file 10 &&
+       git branch -M second &&
        git checkout -b other HEAD~5 &&
        test_commit_bulk --id=side 10 &&
-       git checkout master &&
-       bitmaptip=$(git rev-parse master) &&
+
+       # add complicated history setup, including merges and
+       # ambiguous merge-bases
+
+       git checkout -b merge-left other~2 &&
+       git merge second~2 -m "merge-left" &&
+
+       git checkout -b merge-right second~1 &&
+       git merge other~1 -m "merge-right" &&
+
+       git checkout -b octo-second second &&
+       git merge merge-left merge-right -m "octopus-second" &&
+
+       git checkout -b octo-other other &&
+       git merge merge-left merge-right -m "octopus-other" &&
+
+       git checkout other &&
+       git merge octo-other -m "pull octopus" &&
+
+       git checkout second &&
+       git merge octo-second -m "pull octopus" &&
+
+       # Remove these branches so they are not selected
+       # as bitmap tips
+       git branch -D merge-left &&
+       git branch -D merge-right &&
+       git branch -D octo-other &&
+       git branch -D octo-second &&
+
+       # add padding to make these merges less interesting
+       # and avoid having them selected for bitmaps
+       test_commit_bulk --id=file 100 &&
+       git checkout other &&
+       test_commit_bulk --id=side 100 &&
+       git checkout second &&
+
+       bitmaptip=$(git rev-parse second) &&
        blob=$(echo tagged-blob | git hash-object -w --stdin) &&
        git tag tagged-blob $blob &&
        git config repack.writebitmaps true
 '
 
 test_expect_success 'full repack creates bitmaps' '
-       git repack -ad &&
+       GIT_TRACE2_EVENT_NESTING=4 GIT_TRACE2_EVENT="$(pwd)/trace" \
+               git repack -ad &&
        ls .git/objects/pack/ | grep bitmap >output &&
-       test_line_count = 1 output
+       test_line_count = 1 output &&
+       grep "\"key\":\"num_selected_commits\",\"value\":\"106\"" trace &&
+       grep "\"key\":\"num_maximal_commits\",\"value\":\"107\"" trace
 '
 
 test_expect_success 'rev-list --test-bitmap verifies bitmaps' '
        git rev-list --test-bitmap HEAD
 '
 
-rev_list_tests() {
-       state=$1
-
-       test_expect_success "counting commits via bitmap ($state)" '
-               git rev-list --count HEAD >expect &&
-               git rev-list --use-bitmap-index --count HEAD >actual &&
+rev_list_tests_head () {
+       test_expect_success "counting commits via bitmap ($state, $branch)" '
+               git rev-list --count $branch >expect &&
+               git rev-list --use-bitmap-index --count $branch >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "counting partial commits via bitmap ($state)" '
-               git rev-list --count HEAD~5..HEAD >expect &&
-               git rev-list --use-bitmap-index --count HEAD~5..HEAD >actual &&
+       test_expect_success "counting partial commits via bitmap ($state, $branch)" '
+               git rev-list --count $branch~5..$branch >expect &&
+               git rev-list --use-bitmap-index --count $branch~5..$branch >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "counting commits with limit ($state)" '
-               git rev-list --count -n 1 HEAD >expect &&
-               git rev-list --use-bitmap-index --count -n 1 HEAD >actual &&
+       test_expect_success "counting commits with limit ($state, $branch)" '
+               git rev-list --count -n 1 $branch >expect &&
+               git rev-list --use-bitmap-index --count -n 1 $branch >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "counting non-linear history ($state)" '
-               git rev-list --count other...master >expect &&
-               git rev-list --use-bitmap-index --count other...master >actual &&
+       test_expect_success "counting non-linear history ($state, $branch)" '
+               git rev-list --count other...second >expect &&
+               git rev-list --use-bitmap-index --count other...second >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "counting commits with limiting ($state)" '
-               git rev-list --count HEAD -- 1.t >expect &&
-               git rev-list --use-bitmap-index --count HEAD -- 1.t >actual &&
+       test_expect_success "counting commits with limiting ($state, $branch)" '
+               git rev-list --count $branch -- 1.t >expect &&
+               git rev-list --use-bitmap-index --count $branch -- 1.t >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "counting objects via bitmap ($state)" '
-               git rev-list --count --objects HEAD >expect &&
-               git rev-list --use-bitmap-index --count --objects HEAD >actual &&
+       test_expect_success "counting objects via bitmap ($state, $branch)" '
+               git rev-list --count --objects $branch >expect &&
+               git rev-list --use-bitmap-index --count --objects $branch >actual &&
                test_cmp expect actual
        '
 
-       test_expect_success "enumerate commits ($state)" '
-               git rev-list --use-bitmap-index HEAD >actual &&
-               git rev-list HEAD >expect &&
+       test_expect_success "enumerate commits ($state, $branch)" '
+               git rev-list --use-bitmap-index $branch >actual &&
+               git rev-list $branch >expect &&
                test_bitmap_traversal --no-confirm-bitmaps expect actual
        '
 
-       test_expect_success "enumerate --objects ($state)" '
-               git rev-list --objects --use-bitmap-index HEAD >actual &&
-               git rev-list --objects HEAD >expect &&
+       test_expect_success "enumerate --objects ($state, $branch)" '
+               git rev-list --objects --use-bitmap-index $branch >actual &&
+               git rev-list --objects $branch >expect &&
                test_bitmap_traversal expect actual
        '
 
-       test_expect_success "bitmap --objects handles non-commit objects ($state)" '
-               git rev-list --objects --use-bitmap-index HEAD tagged-blob >actual &&
+       test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
+               git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
                grep $blob actual
        '
 }
 
+rev_list_tests () {
+       state=$1
+
+       for branch in "second" "other"
+       do
+               rev_list_tests_head
+       done
+}
+
 rev_list_tests 'full bitmap'
 
 test_expect_success 'clone from bitmapped repository' '
@@ -128,7 +216,7 @@ test_expect_success 'setup further non-bitmapped commits' '
 rev_list_tests 'partial bitmap'
 
 test_expect_success 'fetch (partial bitmap)' '
-       git --git-dir=clone.git fetch origin master:master &&
+       git --git-dir=clone.git fetch origin second:second &&
        git rev-parse HEAD >expect &&
        git --git-dir=clone.git rev-parse HEAD >actual &&
        test_cmp expect actual
@@ -230,7 +318,7 @@ test_expect_success 'full repack, reusing previous bitmaps' '
 '
 
 test_expect_success 'fetch (full bitmap)' '
-       git --git-dir=clone.git fetch origin master:master &&
+       git --git-dir=clone.git fetch origin second:second &&
        git rev-parse HEAD >expect &&
        git --git-dir=clone.git rev-parse HEAD >actual &&
        test_cmp expect actual
@@ -343,7 +431,20 @@ test_expect_success 'pack reuse respects --incremental' '
        test_must_be_empty actual
 '
 
-test_expect_success 'truncated bitmap fails gracefully' '
+test_expect_success 'truncated bitmap fails gracefully (ewah)' '
+       test_config pack.writebitmaphashcache false &&
+       git repack -ad &&
+       git rev-list --use-bitmap-index --count --all >expect &&
+       bitmap=$(ls .git/objects/pack/*.bitmap) &&
+       test_when_finished "rm -f $bitmap" &&
+       test_copy_bytes 256 <$bitmap >$bitmap.tmp &&
+       mv -f $bitmap.tmp $bitmap &&
+       git rev-list --use-bitmap-index --count --all >actual 2>stderr &&
+       test_cmp expect actual &&
+       test_i18ngrep corrupt.ewah.bitmap stderr
+'
+
+test_expect_success 'truncated bitmap fails gracefully (cache)' '
        git repack -ad &&
        git rev-list --use-bitmap-index --count --all >expect &&
        bitmap=$(ls .git/objects/pack/*.bitmap) &&
@@ -352,7 +453,7 @@ test_expect_success 'truncated bitmap fails gracefully' '
        mv -f $bitmap.tmp $bitmap &&
        git rev-list --use-bitmap-index --count --all >actual 2>stderr &&
        test_cmp expect actual &&
-       test_i18ngrep corrupt stderr
+       test_i18ngrep corrupted.bitmap.index stderr
 '
 
 # have_delta <obj> <expected_base>
index a877dd145e24c56e7197251ebccaa5804ed40522..53d7b8ed7571f54d4c714f6945a62ab8ced3d86f 100755 (executable)
@@ -722,4 +722,121 @@ test_expect_success 'fetch new submodule commit intermittently referenced by sup
        )
 '
 
+add_commit_push () {
+       dir="$1" &&
+       msg="$2" &&
+       shift 2 &&
+       git -C "$dir" add "$@" &&
+       git -C "$dir" commit -a -m "$msg" &&
+       git -C "$dir" push
+}
+
+compare_refs_in_dir () {
+       fail= &&
+       if test "x$1" = 'x!'
+       then
+               fail='!' &&
+               shift
+       fi &&
+       git -C "$1" rev-parse --verify "$2" >expect &&
+       git -C "$3" rev-parse --verify "$4" >actual &&
+       eval $fail test_cmp expect actual
+}
+
+
+test_expect_success 'setup nested submodule fetch test' '
+       # does not depend on any previous test setups
+
+       for repo in outer middle inner
+       do
+               git init --bare $repo &&
+               git clone $repo ${repo}_content &&
+               echo "$repo" >"${repo}_content/file" &&
+               add_commit_push ${repo}_content "initial" file ||
+               return 1
+       done &&
+
+       git clone outer A &&
+       git -C A submodule add "$pwd/middle" &&
+       git -C A/middle/ submodule add "$pwd/inner" &&
+       add_commit_push A/middle/ "adding inner sub" .gitmodules inner &&
+       add_commit_push A/ "adding middle sub" .gitmodules middle &&
+
+       git clone outer B &&
+       git -C B/ submodule update --init middle &&
+
+       compare_refs_in_dir A HEAD B HEAD &&
+       compare_refs_in_dir A/middle HEAD B/middle HEAD &&
+       test_path_is_file B/file &&
+       test_path_is_file B/middle/file &&
+       test_path_is_missing B/middle/inner/file &&
+
+       echo "change on inner repo of A" >"A/middle/inner/file" &&
+       add_commit_push A/middle/inner "change on inner" file &&
+       add_commit_push A/middle "change on inner" inner &&
+       add_commit_push A "change on inner" middle
+'
+
+test_expect_success 'fetching a superproject containing an uninitialized sub/sub project' '
+       # depends on previous test for setup
+
+       git -C B/ fetch &&
+       compare_refs_in_dir A origin/HEAD B origin/HEAD
+'
+
+fetch_with_recursion_abort () {
+       # In a regression the following git call will run into infinite recursion.
+       # To handle that, we connect the sed command to the git call by a pipe
+       # so that sed can kill the infinite recursion when detected.
+       # The recursion creates git output like:
+       # Fetching submodule sub
+       # Fetching submodule sub/sub              <-- [1]
+       # Fetching submodule sub/sub/sub
+       # ...
+       # [1] sed will stop reading and cause git to eventually stop and die
+
+       git -C "$1" fetch --recurse-submodules 2>&1 |
+               sed "/Fetching submodule $2[^$]/q" >out &&
+       ! grep "Fetching submodule $2[^$]" out
+}
+
+test_expect_success 'setup recursive fetch with uninit submodule' '
+       # does not depend on any previous test setups
+
+       test_create_repo super &&
+       test_commit -C super initial &&
+       test_create_repo sub &&
+       test_commit -C sub initial &&
+       git -C sub rev-parse HEAD >expect &&
+
+       git -C super submodule add ../sub &&
+       git -C super commit -m "add sub" &&
+
+       git clone super superclone &&
+       git -C superclone submodule status >out &&
+       sed -e "s/^-//" -e "s/ sub.*$//" out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'recursive fetch with uninit submodule' '
+       # depends on previous test for setup
+
+       fetch_with_recursion_abort superclone sub &&
+       git -C superclone submodule status >out &&
+       sed -e "s/^-//" -e "s/ sub$//" out >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'recursive fetch after deinit a submodule' '
+       # depends on previous test for setup
+
+       git -C superclone submodule update --init sub &&
+       git -C superclone submodule deinit -f sub &&
+
+       fetch_with_recursion_abort superclone sub &&
+       git -C superclone submodule status >out &&
+       sed -e "s/^-//" -e "s/ sub$//" out >actual &&
+       test_cmp expect actual
+'
+
 test_done
index aa226381be214a43e7a07aa2ffe897314938184a..52614eefc7e766b0c236526a5ee9bca1815241a5 100755 (executable)
@@ -926,14 +926,14 @@ test_expect_success 'git bisect reset cleans bisection state properly' '
        git bisect bad $HASH4 &&
        git bisect reset &&
        test -z "$(git for-each-ref "refs/bisect/*")" &&
-       test_path_is_missing "$GIT_DIR/BISECT_EXPECTED_REV" &&
-       test_path_is_missing "$GIT_DIR/BISECT_ANCESTORS_OK" &&
-       test_path_is_missing "$GIT_DIR/BISECT_LOG" &&
-       test_path_is_missing "$GIT_DIR/BISECT_RUN" &&
-       test_path_is_missing "$GIT_DIR/BISECT_TERMS" &&
-       test_path_is_missing "$GIT_DIR/head-name" &&
-       test_path_is_missing "$GIT_DIR/BISECT_HEAD" &&
-       test_path_is_missing "$GIT_DIR/BISECT_START"
+       test_path_is_missing ".git/BISECT_EXPECTED_REV" &&
+       test_path_is_missing ".git/BISECT_ANCESTORS_OK" &&
+       test_path_is_missing ".git/BISECT_LOG" &&
+       test_path_is_missing ".git/BISECT_RUN" &&
+       test_path_is_missing ".git/BISECT_TERMS" &&
+       test_path_is_missing ".git/head-name" &&
+       test_path_is_missing ".git/BISECT_HEAD" &&
+       test_path_is_missing ".git/BISECT_START"
 '
 
 test_done
index 6774e9d86fa04c901b3833127e72f63a60028236..52e8ccc933ad1269d62fc91348700558dc9b95ef 100755 (executable)
@@ -29,11 +29,8 @@ test_expect_success 'setup' '
 
 test_expect_success 'pull.rebase not set' '
        git reset --hard c0 &&
-       git -c color.advice=always pull . c1 2>err &&
-       test_decode_color <err >decoded &&
-       test_i18ngrep "<YELLOW>hint: " decoded &&
-       test_i18ngrep "Pulling without specifying how to reconcile" decoded
-
+       git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
 '
 
 test_expect_success 'pull.rebase not set and pull.ff=true' '
@@ -87,6 +84,65 @@ test_expect_success 'pull.rebase not set and --ff-only given' '
        test_i18ngrep ! "Pulling without specifying how to reconcile" err
 '
 
+test_expect_success 'pull.rebase not set (not-fast-forward)' '
+       git reset --hard c2 &&
+       git -c color.advice=always pull . c1 2>err &&
+       test_decode_color <err >decoded &&
+       test_i18ngrep "<YELLOW>hint: " decoded &&
+       test_i18ngrep "Pulling without specifying how to reconcile" decoded
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '
+       git reset --hard c2 &&
+       test_config pull.ff true &&
+       git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '
+       git reset --hard c2 &&
+       test_config pull.ff false &&
+       git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '
+       git reset --hard c2 &&
+       test_config pull.ff only &&
+       test_must_fail git pull . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '
+       git reset --hard c2 &&
+       git pull --rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '
+       git reset --hard c2 &&
+       git pull --no-rebase . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '
+       git reset --hard c2 &&
+       git pull --ff . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '
+       git reset --hard c2 &&
+       git pull --no-ff . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
+test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '
+       git reset --hard c2 &&
+       test_must_fail git pull --ff-only . c1 2>err &&
+       test_i18ngrep ! "Pulling without specifying how to reconcile" err
+'
+
 test_expect_success 'merge c1 with c2' '
        git reset --hard c1 &&
        test -f c0.c &&
index 3f7391d793c87b9ff9e40572e9c219f9377d9d6a..249ed618ed8e26c966c6d9815db0645c994ae4f5 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -1131,7 +1131,9 @@ static void format_trailer_info(struct strbuf *out,
        size_t i;
 
        /* If we want the whole block untouched, we can take the fast path. */
-       if (!opts->only_trailers && !opts->unfold && !opts->filter && !opts->separator) {
+       if (!opts->only_trailers && !opts->unfold && !opts->filter &&
+           !opts->separator && !opts->key_only && !opts->value_only &&
+           !opts->key_value_separator) {
                strbuf_add(out, info->trailer_start,
                           info->trailer_end - info->trailer_start);
                return;
@@ -1153,8 +1155,15 @@ static void format_trailer_info(struct strbuf *out,
                                if (opts->separator && out->len != origlen)
                                        strbuf_addbuf(out, opts->separator);
                                if (!opts->value_only)
-                                       strbuf_addf(out, "%s: ", tok.buf);
-                               strbuf_addbuf(out, &val);
+                                       strbuf_addbuf(out, &tok);
+                               if (!opts->key_only && !opts->value_only) {
+                                       if (opts->key_value_separator)
+                                               strbuf_addbuf(out, opts->key_value_separator);
+                                       else
+                                               strbuf_addstr(out, ": ");
+                               }
+                               if (!opts->key_only)
+                                       strbuf_addbuf(out, &val);
                                if (!opts->separator)
                                        strbuf_addch(out, '\n');
                        }
index cd93e7ddea789cb6c320af2a7b984bd6f8a8215f..795d2fccfd9579500b798ac667feef2ee549f4b4 100644 (file)
--- a/trailer.h
+++ b/trailer.h
@@ -71,8 +71,10 @@ struct process_trailer_options {
        int only_input;
        int unfold;
        int no_divider;
+       int key_only;
        int value_only;
        const struct strbuf *separator;
+       const struct strbuf *key_value_separator;
        int (*filter)(const struct strbuf *, void *);
        void *filter_data;
 };
diff --git a/tree.c b/tree.c
index e76517f6b180e4703a032706f6d388d491df22a5..a52479812ce0c46ce972a56f0fa1d8bbb7c0b03b 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -144,7 +144,7 @@ int read_tree_recursive(struct repository *r,
        return ret;
 }
 
-static int cmp_cache_name_compare(const void *a_, const void *b_)
+int cmp_cache_name_compare(const void *a_, const void *b_)
 {
        const struct cache_entry *ce1, *ce2;
 
diff --git a/tree.h b/tree.h
index 93837450739c23e69f3040b8747a54ede26df646..3eb0484cbf2a7523d15c2102d138f1622aaecd1a 100644 (file)
--- a/tree.h
+++ b/tree.h
@@ -28,6 +28,8 @@ void free_tree_buffer(struct tree *tree);
 /* Parses and returns the tree in the given ent, chasing tags and commits. */
 struct tree *parse_tree_indirect(const struct object_id *oid);
 
+int cmp_cache_name_compare(const void *a_, const void *b_);
+
 #define READ_TREE_RECURSIVE 1
 typedef int (*read_tree_fn_t)(const struct object_id *, struct strbuf *, const char *, unsigned int, int, void *);
 
index f84ceae87d3847019c45c335f1015ff15b8bfe0e..821b23347955a56ea3ee93fb3307afddf79c0279 100644 (file)
@@ -644,6 +644,42 @@ static int is_main_worktree_path(const char *path)
        return !cmp;
 }
 
+/*
+ * If both the main worktree and linked worktree have been moved, then the
+ * gitfile /path/to/worktree/.git won't point into the repository, thus we
+ * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
+ * be able to infer the gitdir by manually reading /path/to/worktree/.git,
+ * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
+ */
+static char *infer_backlink(const char *gitfile)
+{
+       struct strbuf actual = STRBUF_INIT;
+       struct strbuf inferred = STRBUF_INIT;
+       const char *id;
+
+       if (strbuf_read_file(&actual, gitfile, 0) < 0)
+               goto error;
+       if (!starts_with(actual.buf, "gitdir:"))
+               goto error;
+       if (!(id = find_last_dir_sep(actual.buf)))
+               goto error;
+       strbuf_trim(&actual);
+       id++; /* advance past '/' to point at <id> */
+       if (!*id)
+               goto error;
+       strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
+       if (!is_directory(inferred.buf))
+               goto error;
+
+       strbuf_release(&actual);
+       return strbuf_detach(&inferred, NULL);
+
+error:
+       strbuf_release(&actual);
+       strbuf_release(&inferred);
+       return NULL;
+}
+
 /*
  * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
  * the worktree's path.
@@ -675,6 +711,11 @@ void repair_worktree_at_path(const char *path,
        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 == READ_GITFILE_ERR_NOT_A_REPO) {
+               if (!(backlink = infer_backlink(realdotgit.buf))) {
+                       fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
+                       goto done;
+               }
        } else if (err) {
                fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
                goto done;