Merge branch 'en/merge-recursive-directory-rename-fixes'
authorJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:13 +0000 (18:02 +0900)
committerJunio C Hamano <gitster@pobox.com>
Sun, 10 Nov 2019 09:02:13 +0000 (18:02 +0900)
When all files from some subdirectory were renamed to the root
directory, the directory rename heuristics would fail to detect that
as a rename/merge of the subdirectory to the root directory, which has
been corrected.

* en/merge-recursive-directory-rename-fixes:
  t604[236]: do not run setup in separate tests
  merge-recursive: fix merging a subdirectory into the root directory
  merge-recursive: clean up get_renamed_dir_portion()

161 files changed:
CODE_OF_CONDUCT.md [new file with mode: 0644]
Documentation/Makefile
Documentation/MyFirstObjectWalk.txt [new file with mode: 0644]
Documentation/RelNotes/2.24.0.txt
Documentation/config/fetch.txt
Documentation/config/format.txt
Documentation/config/trace2.txt
Documentation/fetch-options.txt
Documentation/git-fast-export.txt
Documentation/git-fast-import.txt
Documentation/git-format-patch.txt
Documentation/git-notes.txt
Documentation/git-stash.txt
Documentation/gitremote-helpers.txt
Documentation/manpage-bold-literal.xsl
Documentation/technical/api-trace2.txt
Documentation/trace2-target-values.txt
GIT-VERSION-GEN
Makefile
apply.c
attr.c
azure-pipelines.yml
blame.c
builtin/am.c
builtin/blame.c
builtin/checkout.c
builtin/commit-graph.c
builtin/commit.c
builtin/describe.c
builtin/difftool.c
builtin/fast-export.c
builtin/fetch.c
builtin/grep.c
builtin/log.c
builtin/merge-recursive.c
builtin/notes.c
builtin/push.c
builtin/rebase.c
builtin/repack.c
builtin/stash.c
cache-tree.c
cache-tree.h
cache.h
ci/install-dependencies.sh
column.c
commit-graph.c
commit-reach.c
commit.c
compat/mingw.c
compat/vcbuild/scripts/clink.pl
compat/win32/path-utils.h
compat/win32/pthread.h
compat/winansi.c
config.c
config.mak.uname
contrib/buildsystems/Generators/Vcxproj.pm
contrib/coccinelle/hashmap.cocci [new file with mode: 0644]
contrib/completion/git-completion.zsh
contrib/diff-highlight/DiffHighlight.pm
diff.c
diffcore-rename.c
fast-import.c
fetch-pack.c
fsmonitor.c
git-compat-util.h
git-gui/README.md [new file with mode: 0644]
git-gui/git-gui.sh
git-gui/lib/diff.tcl
git-gui/po/ja.po
git-legacy-stash.sh
gitweb/static/js/blame_incremental.js
grep.c
grep.h
hashmap.c
hashmap.h
http-push.c
merge-recursive.c
merge-recursive.h
name-hash.c
object.h
oidmap.c
oidmap.h
packfile.c
patch-ids.c
po/bg.po
po/ca.po
po/de.po
po/es.po
po/fr.po
po/git.pot
po/it.po
po/sv.po
po/vi.po
po/zh_CN.po
quote.c
range-diff.c
read-cache.c
ref-filter.c
refs.c
remote-curl.c
remote.c
remote.h
repo-settings.c
revision.c
send-pack.c
sequencer.c
sequencer.h
sha1-lookup.c
sub-process.c
sub-process.h
submodule-config.c
t/helper/test-hashmap.c
t/helper/test-lazy-init-name-hash.c
t/helper/test-progress.c
t/helper/test-run-command.c
t/lib-rebase.sh
t/t0000-basic.sh
t/t0014-alias.sh
t/t0028-working-tree-encoding.sh
t/t0212-trace2-event.sh
t/t1308-config-set.sh
t/t1600-index.sh
t/t3030-merge-recursive.sh
t/t3206-range-diff.sh
t/t3206/history.export
t/t3301-notes.sh
t/t3404-rebase-interactive.sh
t/t3906-stash-submodule.sh
t/t4014-format-patch.sh
t/t4018/dts-nodes-boolean-prop [new file with mode: 0644]
t/t4018/dts-nodes-multiline-prop [new file with mode: 0644]
t/t4018/dts-root
t/t4018/dts-root-comment [new file with mode: 0644]
t/t4214-log-graph-octopus.sh
t/t5510-fetch.sh
t/t5514-fetch-multiple.sh
t/t5541-http-push-smart.sh
t/t5616-partial-clone.sh
t/t6036-recursive-corner-cases.sh
t/t6047-diff3-conflict-markers.sh [new file with mode: 0755]
t/t7419-submodule-set-branch.sh
t/t7519-status-fsmonitor.sh
t/t7519/fsmonitor-env [new file with mode: 0755]
t/t9300-fast-import.sh
t/t9350-fast-export.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
trace2/tr2_dst.c
trace2/tr2_dst.h
trace2/tr2_sysenv.c
trace2/tr2_sysenv.h
trace2/tr2_tgt_event.c
trace2/tr2_tgt_normal.c
trace2/tr2_tgt_perf.c
transport-helper.c
transport.c
transport.h
userdiff.c
utf8.c
xdiff/xdiffi.c

diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644 (file)
index 0000000..fc4645d
--- /dev/null
@@ -0,0 +1,93 @@
+# Git Code of Conduct
+
+This code of conduct outlines our expectations for participants within
+the Git community, as well as steps for reporting unacceptable behavior.
+We are committed to providing a welcoming and inspiring community for
+all and expect our code of conduct to be honored. Anyone who violates
+this code of conduct may be banned from the community.
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to make participation in our project and
+our community a harassment-free experience for everyone, regardless of age,
+body size, disability, ethnicity, sex characteristics, gender identity and
+expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+  advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies within all project spaces, and it also applies
+when an individual is representing the project or its community in public
+spaces. Examples of representing a project or community include using an
+official project e-mail address, posting via an official social media account,
+or acting as an appointed representative at an online or offline event.
+Representation of a project may be further defined and clarified by project
+maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at git@sfconservancy.org. All
+complaints will be reviewed and investigated and will result in a response
+that is deemed necessary and appropriate to the circumstances. The project
+team is obligated to maintain confidentiality with regard to the reporter of
+an incident. Further details of specific enforcement policies may be posted
+separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+The project leadership team can be contacted by email as a whole at
+git@sfconservancy.org, or individually:
+
+  - Ævar Arnfjörð Bjarmason <avarab@gmail.com>
+  - Christian Couder <christian.couder@gmail.com>
+  - Jeff King <peff@peff.net>
+  - Junio C Hamano <gitster@pobox.com>
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
index 06d85ad..8fe829c 100644 (file)
@@ -77,6 +77,7 @@ API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technica
 SP_ARTICLES += $(API_DOCS)
 
 TECH_DOCS += MyFirstContribution
+TECH_DOCS += MyFirstObjectWalk
 TECH_DOCS += SubmittingPatches
 TECH_DOCS += technical/hash-function-transition
 TECH_DOCS += technical/http-protocol
diff --git a/Documentation/MyFirstObjectWalk.txt b/Documentation/MyFirstObjectWalk.txt
new file mode 100644 (file)
index 0000000..4d24dae
--- /dev/null
@@ -0,0 +1,906 @@
+= My First Object Walk
+
+== What's an Object Walk?
+
+The object walk is a key concept in Git - this is the process that underpins
+operations like object transfer and fsck. Beginning from a given commit, the
+list of objects is found by walking parent relationships between commits (commit
+X based on commit W) and containment relationships between objects (tree Y is
+contained within commit X, and blob Z is located within tree Y, giving our
+working tree for commit X something like `y/z.txt`).
+
+A related concept is the revision walk, which is focused on commit objects and
+their parent relationships and does not delve into other object types. The
+revision walk is used for operations like `git log`.
+
+=== Related Reading
+
+- `Documentation/user-manual.txt` under "Hacking Git" contains some coverage of
+  the revision walker in its various incarnations.
+- `Documentation/technical/api-revision-walking.txt`
+- https://eagain.net/articles/git-for-computer-scientists/[Git for Computer Scientists]
+  gives a good overview of the types of objects in Git and what your object
+  walk is really describing.
+
+== Setting Up
+
+Create a new branch from `master`.
+
+----
+git checkout -b revwalk origin/master
+----
+
+We'll put our fiddling into a new command. For fun, let's name it `git walken`.
+Open up a new file `builtin/walken.c` and set up the command handler:
+
+----
+/*
+ * "git walken"
+ *
+ * Part of the "My First Object Walk" tutorial.
+ */
+
+#include "builtin.h"
+
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       trace_printf(_("cmd_walken incoming...\n"));
+       return 0;
+}
+----
+
+NOTE: `trace_printf()` differs from `printf()` in that it can be turned on or
+off at runtime. For the purposes of this tutorial, we will write `walken` as
+though it is intended for use as a "plumbing" command: that is, a command which
+is used primarily in scripts, rather than interactively by humans (a "porcelain"
+command). So we will send our debug output to `trace_printf()` instead. When
+running, enable trace output by setting the environment variable `GIT_TRACE`.
+
+Add usage text and `-h` handling, like all subcommands should consistently do
+(our test suite will notice and complain if you fail to do so).
+
+----
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       const char * const walken_usage[] = {
+               N_("git walken"),
+               NULL,
+       }
+       struct option options[] = {
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, walken_usage, 0);
+
+       ...
+}
+----
+
+Also add the relevant line in `builtin.h` near `cmd_whatchanged()`:
+
+----
+int cmd_walken(int argc, const char **argv, const char *prefix);
+----
+
+Include the command in `git.c` in `commands[]` near the entry for `whatchanged`,
+maintaining alphabetical ordering:
+
+----
+{ "walken", cmd_walken, RUN_SETUP },
+----
+
+Add it to the `Makefile` near the line for `builtin/worktree.o`:
+
+----
+BUILTIN_OBJS += builtin/walken.o
+----
+
+Build and test out your command, without forgetting to ensure the `DEVELOPER`
+flag is set, and with `GIT_TRACE` enabled so the debug output can be seen:
+
+----
+$ echo DEVELOPER=1 >>config.mak
+$ make
+$ GIT_TRACE=1 ./bin-wrappers/git walken
+----
+
+NOTE: For a more exhaustive overview of the new command process, take a look at
+`Documentation/MyFirstContribution.txt`.
+
+NOTE: A reference implementation can be found at
+https://github.com/nasamuffin/git/tree/revwalk.
+
+=== `struct rev_cmdline_info`
+
+The definition of `struct rev_cmdline_info` can be found in `revision.h`.
+
+This struct is contained within the `rev_info` struct and is used to reflect
+parameters provided by the user over the CLI.
+
+`nr` represents the number of `rev_cmdline_entry` present in the array.
+
+`alloc` is used by the `ALLOC_GROW` macro. Check
+`Documentation/technical/api-allocation-growing.txt` - this variable is used to
+track the allocated size of the list.
+
+Per entry, we find:
+
+`item` is the object provided upon which to base the object walk. Items in Git
+can be blobs, trees, commits, or tags. (See `Documentation/gittutorial-2.txt`.)
+
+`name` is the object ID (OID) of the object - a hex string you may be familiar
+with from using Git to organize your source in the past. Check the tutorial
+mentioned above towards the top for a discussion of where the OID can come
+from.
+
+`whence` indicates some information about what to do with the parents of the
+specified object. We'll explore this flag more later on; take a look at
+`Documentation/revisions.txt` to get an idea of what could set the `whence`
+value.
+
+`flags` are used to hint the beginning of the revision walk and are the first
+block under the `#include`s in `revision.h`. The most likely ones to be set in
+the `rev_cmdline_info` are `UNINTERESTING` and `BOTTOM`, but these same flags
+can be used during the walk, as well.
+
+=== `struct rev_info`
+
+This one is quite a bit longer, and many fields are only used during the walk
+by `revision.c` - not configuration options. Most of the configurable flags in
+`struct rev_info` have a mirror in `Documentation/rev-list-options.txt`. It's a
+good idea to take some time and read through that document.
+
+== Basic Commit Walk
+
+First, let's see if we can replicate the output of `git log --oneline`. We'll
+refer back to the implementation frequently to discover norms when performing
+an object walk of our own.
+
+To do so, we'll first find all the commits, in order, which preceded the current
+commit. We'll extract the name and subject of the commit from each.
+
+Ideally, we will also be able to find out which ones are currently at the tip of
+various branches.
+
+=== Setting Up
+
+Preparing for your object walk has some distinct stages.
+
+1. Perform default setup for this mode, and others which may be invoked.
+2. Check configuration files for relevant settings.
+3. Set up the `rev_info` struct.
+4. Tweak the initialized `rev_info` to suit the current walk.
+5. Prepare the `rev_info` for the walk.
+6. Iterate over the objects, processing each one.
+
+==== Default Setups
+
+Before examining configuration files which may modify command behavior, set up
+default state for switches or options your command may have. If your command
+utilizes other Git components, ask them to set up their default states as well.
+For instance, `git log` takes advantage of `grep` and `diff` functionality, so
+its `init_log_defaults()` sets its own state (`decoration_style`) and asks
+`grep` and `diff` to initialize themselves by calling each of their
+initialization functions.
+
+For our first example within `git walken`, we don't intend to use any other
+components within Git, and we don't have any configuration to do.  However, we
+may want to add some later, so for now, we can add an empty placeholder. Create
+a new function in `builtin/walken.c`:
+
+----
+static void init_walken_defaults(void)
+{
+       /*
+        * We don't actually need the same components `git log` does; leave this
+        * empty for now.
+        */
+}
+----
+
+Make sure to add a line invoking it inside of `cmd_walken()`.
+
+----
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       init_walken_defaults();
+}
+----
+
+==== Configuring From `.gitconfig`
+
+Next, we should have a look at any relevant configuration settings (i.e.,
+settings readable and settable from `git config`). This is done by providing a
+callback to `git_config()`; within that callback, you can also invoke methods
+from other components you may need that need to intercept these options. Your
+callback will be invoked once per each configuration value which Git knows about
+(global, local, worktree, etc.).
+
+Similarly to the default values, we don't have anything to do here yet
+ourselves; however, we should call `git_default_config()` if we aren't calling
+any other existing config callbacks.
+
+Add a new function to `builtin/walken.c`:
+
+----
+static int git_walken_config(const char *var, const char *value, void *cb)
+{
+       /*
+        * For now, we don't have any custom configuration, so fall back to
+        * the default config.
+        */
+       return git_default_config(var, value, cb);
+}
+----
+
+Make sure to invoke `git_config()` with it in your `cmd_walken()`:
+
+----
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       ...
+
+       git_config(git_walken_config, NULL);
+
+       ...
+}
+----
+
+==== Setting Up `rev_info`
+
+Now that we've gathered external configuration and options, it's time to
+initialize the `rev_info` object which we will use to perform the walk. This is
+typically done by calling `repo_init_revisions()` with the repository you intend
+to target, as well as the `prefix` argument of `cmd_walken` and your `rev_info`
+struct.
+
+Add the `struct rev_info` and the `repo_init_revisions()` call:
+----
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       /* This can go wherever you like in your declarations.*/
+       struct rev_info rev;
+       ...
+
+       /* This should go after the git_config() call. */
+       repo_init_revisions(the_repository, &rev, prefix);
+
+       ...
+}
+----
+
+==== Tweaking `rev_info` For the Walk
+
+We're getting close, but we're still not quite ready to go. Now that `rev` is
+initialized, we can modify it to fit our needs. This is usually done within a
+helper for clarity, so let's add one:
+
+----
+static void final_rev_info_setup(struct rev_info *rev)
+{
+       /*
+        * We want to mimic the appearance of `git log --oneline`, so let's
+        * force oneline format.
+        */
+       get_commit_format("oneline", rev);
+
+       /* Start our object walk at HEAD. */
+       add_head_to_pending(rev);
+}
+----
+
+[NOTE]
+====
+Instead of using the shorthand `add_head_to_pending()`, you could do
+something like this:
+----
+       struct setup_revision_opt opt;
+
+       memset(&opt, 0, sizeof(opt));
+       opt.def = "HEAD";
+       opt.revarg_opt = REVARG_COMMITTISH;
+       setup_revisions(argc, argv, rev, &opt);
+----
+Using a `setup_revision_opt` gives you finer control over your walk's starting
+point.
+====
+
+Then let's invoke `final_rev_info_setup()` after the call to
+`repo_init_revisions()`:
+
+----
+int cmd_walken(int argc, const char **argv, const char *prefix)
+{
+       ...
+
+       final_rev_info_setup(&rev);
+
+       ...
+}
+----
+
+Later, we may wish to add more arguments to `final_rev_info_setup()`. But for
+now, this is all we need.
+
+==== Preparing `rev_info` For the Walk
+
+Now that `rev` is all initialized and configured, we've got one more setup step
+before we get rolling. We can do this in a helper, which will both prepare the
+`rev_info` for the walk, and perform the walk itself. Let's start the helper
+with the call to `prepare_revision_walk()`, which can return an error without
+dying on its own:
+
+----
+static void walken_commit_walk(struct rev_info *rev)
+{
+       if (prepare_revision_walk(rev))
+               die(_("revision walk setup failed"));
+}
+----
+
+NOTE: `die()` prints to `stderr` and exits the program. Since it will print to
+`stderr` it's likely to be seen by a human, so we will localize it.
+
+==== Performing the Walk!
+
+Finally! We are ready to begin the walk itself. Now we can see that `rev_info`
+can also be used as an iterator; we move to the next item in the walk by using
+`get_revision()` repeatedly. Add the listed variable declarations at the top and
+the walk loop below the `prepare_revision_walk()` call within your
+`walken_commit_walk()`:
+
+----
+static void walken_commit_walk(struct rev_info *rev)
+{
+       struct commit *commit;
+       struct strbuf prettybuf = STRBUF_INIT;
+
+       ...
+
+       while ((commit = get_revision(rev))) {
+               if (!commit)
+                       continue;
+
+               strbuf_reset(&prettybuf);
+               pp_commit_easy(CMIT_FMT_ONELINE, commit, &prettybuf);
+               puts(prettybuf.buf);
+       }
+       strbuf_release(&prettybuf);
+}
+----
+
+NOTE: `puts()` prints a `char*` to `stdout`. Since this is the part of the
+command we expect to be machine-parsed, we're sending it directly to stdout.
+
+Give it a shot.
+
+----
+$ make
+$ ./bin-wrappers/git walken
+----
+
+You should see all of the subject lines of all the commits in
+your tree's history, in order, ending with the initial commit, "Initial revision
+of "git", the information manager from hell". Congratulations! You've written
+your first revision walk. You can play with printing some additional fields
+from each commit if you're curious; have a look at the functions available in
+`commit.h`.
+
+=== Adding a Filter
+
+Next, let's try to filter the commits we see based on their author. This is
+equivalent to running `git log --author=<pattern>`. We can add a filter by
+modifying `rev_info.grep_filter`, which is a `struct grep_opt`.
+
+First some setup. Add `init_grep_defaults()` to `init_walken_defaults()` and add
+`grep_config()` to `git_walken_config()`:
+
+----
+static void init_walken_defaults(void)
+{
+       init_grep_defaults(the_repository);
+}
+
+...
+
+static int git_walken_config(const char *var, const char *value, void *cb)
+{
+       grep_config(var, value, cb);
+       return git_default_config(var, value, cb);
+}
+----
+
+Next, we can modify the `grep_filter`. This is done with convenience functions
+found in `grep.h`. For fun, we're filtering to only commits from folks using a
+`gmail.com` email address - a not-very-precise guess at who may be working on
+Git as a hobby. Since we're checking the author, which is a specific line in the
+header, we'll use the `append_header_grep_pattern()` helper. We can use
+the `enum grep_header_field` to indicate which part of the commit header we want
+to search.
+
+In `final_rev_info_setup()`, add your filter line:
+
+----
+static void final_rev_info_setup(int argc, const char **argv,
+               const char *prefix, struct rev_info *rev)
+{
+       ...
+
+       append_header_grep_pattern(&rev->grep_filter, GREP_HEADER_AUTHOR,
+               "gmail");
+       compile_grep_patterns(&rev->grep_filter);
+
+       ...
+}
+----
+
+`append_header_grep_pattern()` adds your new "gmail" pattern to `rev_info`, but
+it won't work unless we compile it with `compile_grep_patterns()`.
+
+NOTE: If you are using `setup_revisions()` (for example, if you are passing a
+`setup_revision_opt` instead of using `add_head_to_pending()`), you don't need
+to call `compile_grep_patterns()` because `setup_revisions()` calls it for you.
+
+NOTE: We could add the same filter via the `append_grep_pattern()` helper if we
+wanted to, but `append_header_grep_pattern()` adds the `enum grep_context` and
+`enum grep_pat_token` for us.
+
+=== Changing the Order
+
+There are a few ways that we can change the order of the commits during a
+revision walk. Firstly, we can use the `enum rev_sort_order` to choose from some
+typical orderings.
+
+`topo_order` is the same as `git log --topo-order`: we avoid showing a parent
+before all of its children have been shown, and we avoid mixing commits which
+are in different lines of history. (`git help log`'s section on `--topo-order`
+has a very nice diagram to illustrate this.)
+
+Let's see what happens when we run with `REV_SORT_BY_COMMIT_DATE` as opposed to
+`REV_SORT_BY_AUTHOR_DATE`. Add the following:
+
+----
+static void final_rev_info_setup(int argc, const char **argv,
+               const char *prefix, struct rev_info *rev)
+{
+       ...
+
+       rev->topo_order = 1;
+       rev->sort_order = REV_SORT_BY_COMMIT_DATE;
+
+       ...
+}
+----
+
+Let's output this into a file so we can easily diff it with the walk sorted by
+author date.
+
+----
+$ make
+$ ./bin-wrappers/git walken > commit-date.txt
+----
+
+Then, let's sort by author date and run it again.
+
+----
+static void final_rev_info_setup(int argc, const char **argv,
+               const char *prefix, struct rev_info *rev)
+{
+       ...
+
+       rev->topo_order = 1;
+       rev->sort_order = REV_SORT_BY_AUTHOR_DATE;
+
+       ...
+}
+----
+
+----
+$ make
+$ ./bin-wrappers/git walken > author-date.txt
+----
+
+Finally, compare the two. This is a little less helpful without object names or
+dates, but hopefully we get the idea.
+
+----
+$ diff -u commit-date.txt author-date.txt
+----
+
+This display indicates that commits can be reordered after they're written, for
+example with `git rebase`.
+
+Let's try one more reordering of commits. `rev_info` exposes a `reverse` flag.
+Set that flag somewhere inside of `final_rev_info_setup()`:
+
+----
+static void final_rev_info_setup(int argc, const char **argv, const char *prefix,
+               struct rev_info *rev)
+{
+       ...
+
+       rev->reverse = 1;
+
+       ...
+}
+----
+
+Run your walk again and note the difference in order. (If you remove the grep
+pattern, you should see the last commit this call gives you as your current
+HEAD.)
+
+== Basic Object Walk
+
+So far we've been walking only commits. But Git has more types of objects than
+that! Let's see if we can walk _all_ objects, and find out some information
+about each one.
+
+We can base our work on an example. `git pack-objects` prepares all kinds of
+objects for packing into a bitmap or packfile. The work we are interested in
+resides in `builtins/pack-objects.c:get_object_list()`; examination of that
+function shows that the all-object walk is being performed by
+`traverse_commit_list()` or `traverse_commit_list_filtered()`. Those two
+functions reside in `list-objects.c`; examining the source shows that, despite
+the name, these functions traverse all kinds of objects. Let's have a look at
+the arguments to `traverse_commit_list_filtered()`, which are a superset of the
+arguments to the unfiltered version.
+
+- `struct list_objects_filter_options *filter_options`: This is a struct which
+  stores a filter-spec as outlined in `Documentation/rev-list-options.txt`.
+- `struct rev_info *revs`: This is the `rev_info` used for the walk.
+- `show_commit_fn show_commit`: A callback which will be used to handle each
+  individual commit object.
+- `show_object_fn show_object`: A callback which will be used to handle each
+  non-commit object (so each blob, tree, or tag).
+- `void *show_data`: A context buffer which is passed in turn to `show_commit`
+  and `show_object`.
+- `struct oidset *omitted`: A linked-list of object IDs which the provided
+  filter caused to be omitted.
+
+It looks like this `traverse_commit_list_filtered()` uses callbacks we provide
+instead of needing us to call it repeatedly ourselves. Cool! Let's add the
+callbacks first.
+
+For the sake of this tutorial, we'll simply keep track of how many of each kind
+of object we find. At file scope in `builtin/walken.c` add the following
+tracking variables:
+
+----
+static int commit_count;
+static int tag_count;
+static int blob_count;
+static int tree_count;
+----
+
+Commits are handled by a different callback than other objects; let's do that
+one first:
+
+----
+static void walken_show_commit(struct commit *cmt, void *buf)
+{
+       commit_count++;
+}
+----
+
+The `cmt` argument is fairly self-explanatory. But it's worth mentioning that
+the `buf` argument is actually the context buffer that we can provide to the
+traversal calls - `show_data`, which we mentioned a moment ago.
+
+Since we have the `struct commit` object, we can look at all the same parts that
+we looked at in our earlier commit-only walk. For the sake of this tutorial,
+though, we'll just increment the commit counter and move on.
+
+The callback for non-commits is a little different, as we'll need to check
+which kind of object we're dealing with:
+
+----
+static void walken_show_object(struct object *obj, const char *str, void *buf)
+{
+       switch (obj->type) {
+       case OBJ_TREE:
+               tree_count++;
+               break;
+       case OBJ_BLOB:
+               blob_count++;
+               break;
+       case OBJ_TAG:
+               tag_count++;
+               break;
+       case OBJ_COMMIT:
+               BUG("unexpected commit object in walken_show_object\n");
+       default:
+               BUG("unexpected object type %s in walken_show_object\n",
+                       type_name(obj->type));
+       }
+}
+----
+
+Again, `obj` is fairly self-explanatory, and we can guess that `buf` is the same
+context pointer that `walken_show_commit()` receives: the `show_data` argument
+to `traverse_commit_list()` and `traverse_commit_list_filtered()`. Finally,
+`str` contains the name of the object, which ends up being something like
+`foo.txt` (blob), `bar/baz` (tree), or `v1.2.3` (tag).
+
+To help assure us that we aren't double-counting commits, we'll include some
+complaining if a commit object is routed through our non-commit callback; we'll
+also complain if we see an invalid object type. Since those two cases should be
+unreachable, and would only change in the event of a semantic change to the Git
+codebase, we complain by using `BUG()` - which is a signal to a developer that
+the change they made caused unintended consequences, and the rest of the
+codebase needs to be updated to understand that change. `BUG()` is not intended
+to be seen by the public, so it is not localized.
+
+Our main object walk implementation is substantially different from our commit
+walk implementation, so let's make a new function to perform the object walk. We
+can perform setup which is applicable to all objects here, too, to keep separate
+from setup which is applicable to commit-only walks.
+
+We'll start by enabling all types of objects in the `struct rev_info`.  We'll
+also turn on `tree_blobs_in_commit_order`, which means that we will walk a
+commit's tree and everything it points to immediately after we find each commit,
+as opposed to waiting for the end and walking through all trees after the commit
+history has been discovered. With the appropriate settings configured, we are
+ready to call `prepare_revision_walk()`.
+
+----
+static void walken_object_walk(struct rev_info *rev)
+{
+       rev->tree_objects = 1;
+       rev->blob_objects = 1;
+       rev->tag_objects = 1;
+       rev->tree_blobs_in_commit_order = 1;
+
+       if (prepare_revision_walk(rev))
+               die(_("revision walk setup failed"));
+
+       commit_count = 0;
+       tag_count = 0;
+       blob_count = 0;
+       tree_count = 0;
+----
+
+Let's start by calling just the unfiltered walk and reporting our counts.
+Complete your implementation of `walken_object_walk()`:
+
+----
+       traverse_commit_list(rev, walken_show_commit, walken_show_object, NULL);
+
+       printf("commits %d\nblobs %d\ntags %d\ntrees %d\n", commit_count,
+               blob_count, tag_count, tree_count);
+}
+----
+
+NOTE: This output is intended to be machine-parsed. Therefore, we are not
+sending it to `trace_printf()`, and we are not localizing it - we need scripts
+to be able to count on the formatting to be exactly the way it is shown here.
+If we were intending this output to be read by humans, we would need to localize
+it with `_()`.
+
+Finally, we'll ask `cmd_walken()` to use the object walk instead. Discussing
+command line options is out of scope for this tutorial, so we'll just hardcode
+a branch we can change at compile time. Where you call `final_rev_info_setup()`
+and `walken_commit_walk()`, instead branch like so:
+
+----
+       if (1) {
+               add_head_to_pending(&rev);
+               walken_object_walk(&rev);
+       } else {
+               final_rev_info_setup(argc, argv, prefix, &rev);
+               walken_commit_walk(&rev);
+       }
+----
+
+NOTE: For simplicity, we've avoided all the filters and sorts we applied in
+`final_rev_info_setup()` and simply added `HEAD` to our pending queue. If you
+want, you can certainly use the filters we added before by moving
+`final_rev_info_setup()` out of the conditional and removing the call to
+`add_head_to_pending()`.
+
+Now we can try to run our command! It should take noticeably longer than the
+commit walk, but an examination of the output will give you an idea why. Your
+output should look similar to this example, but with different counts:
+
+----
+Object walk completed. Found 55733 commits, 100274 blobs, 0 tags, and 104210 trees.
+----
+
+This makes sense. We have more trees than commits because the Git project has
+lots of subdirectories which can change, plus at least one tree per commit. We
+have no tags because we started on a commit (`HEAD`) and while tags can point to
+commits, commits can't point to tags.
+
+NOTE: You will have different counts when you run this yourself! The number of
+objects grows along with the Git project.
+
+=== Adding a Filter
+
+There are a handful of filters that we can apply to the object walk laid out in
+`Documentation/rev-list-options.txt`. These filters are typically useful for
+operations such as creating packfiles or performing a partial clone. They are
+defined in `list-objects-filter-options.h`. For the purposes of this tutorial we
+will use the "tree:1" filter, which causes the walk to omit all trees and blobs
+which are not directly referenced by commits reachable from the commit in
+`pending` when the walk begins. (`pending` is the list of objects which need to
+be traversed during a walk; you can imagine a breadth-first tree traversal to
+help understand. In our case, that means we omit trees and blobs not directly
+referenced by `HEAD` or `HEAD`'s history, because we begin the walk with only
+`HEAD` in the `pending` list.)
+
+First, we'll need to `#include "list-objects-filter-options.h`" and set up the
+`struct list_objects_filter_options` at the top of the function.
+
+----
+static void walken_object_walk(struct rev_info *rev)
+{
+       struct list_objects_filter_options filter_options = {};
+
+       ...
+----
+
+For now, we are not going to track the omitted objects, so we'll replace those
+parameters with `NULL`. For the sake of simplicity, we'll add a simple
+build-time branch to use our filter or not. Replace the line calling
+`traverse_commit_list()` with the following, which will remind us which kind of
+walk we've just performed:
+
+----
+       if (0) {
+               /* Unfiltered: */
+               trace_printf(_("Unfiltered object walk.\n"));
+               traverse_commit_list(rev, walken_show_commit,
+                               walken_show_object, NULL);
+       } else {
+               trace_printf(
+                       _("Filtered object walk with filterspec 'tree:1'.\n"));
+               parse_list_objects_filter(&filter_options, "tree:1");
+
+               traverse_commit_list_filtered(&filter_options, rev,
+                       walken_show_commit, walken_show_object, NULL, NULL);
+       }
+----
+
+`struct list_objects_filter_options` is usually built directly from a command
+line argument, so the module provides an easy way to build one from a string.
+Even though we aren't taking user input right now, we can still build one with
+a hardcoded string using `parse_list_objects_filter()`.
+
+With the filter spec "tree:1", we are expecting to see _only_ the root tree for
+each commit; therefore, the tree object count should be less than or equal to
+the number of commits. (For an example of why that's true: `git commit --revert`
+points to the same tree object as its grandparent.)
+
+=== Counting Omitted Objects
+
+We also have the capability to enumerate all objects which were omitted by a
+filter, like with `git log --filter=<spec> --filter-print-omitted`. Asking
+`traverse_commit_list_filtered()` to populate the `omitted` list means that our
+object walk does not perform any better than an unfiltered object walk; all
+reachable objects are walked in order to populate the list.
+
+First, add the `struct oidset` and related items we will use to iterate it:
+
+----
+static void walken_object_walk(
+       ...
+
+       struct oidset omitted;
+       struct oidset_iter oit;
+       struct object_id *oid = NULL;
+       int omitted_count = 0;
+       oidset_init(&omitted, 0);
+
+       ...
+----
+
+Modify the call to `traverse_commit_list_filtered()` to include your `omitted`
+object:
+
+----
+       ...
+
+               traverse_commit_list_filtered(&filter_options, rev,
+                       walken_show_commit, walken_show_object, NULL, &omitted);
+
+       ...
+----
+
+Then, after your traversal, the `oidset` traversal is pretty straightforward.
+Count all the objects within and modify the print statement:
+
+----
+       /* Count the omitted objects. */
+       oidset_iter_init(&omitted, &oit);
+
+       while ((oid = oidset_iter_next(&oit)))
+               omitted_count++;
+
+       printf("commits %d\nblobs %d\ntags %d\ntrees%d\nomitted %d\n",
+               commit_count, blob_count, tag_count, tree_count, omitted_count);
+----
+
+By running your walk with and without the filter, you should find that the total
+object count in each case is identical. You can also time each invocation of
+the `walken` subcommand, with and without `omitted` being passed in, to confirm
+to yourself the runtime impact of tracking all omitted objects.
+
+=== Changing the Order
+
+Finally, let's demonstrate that you can also reorder walks of all objects, not
+just walks of commits. First, we'll make our handlers chattier - modify
+`walken_show_commit()` and `walken_show_object()` to print the object as they
+go:
+
+----
+static void walken_show_commit(struct commit *cmt, void *buf)
+{
+       trace_printf("commit: %s\n", oid_to_hex(&cmt->object.oid));
+       commit_count++;
+}
+
+static void walken_show_object(struct object *obj, const char *str, void *buf)
+{
+       trace_printf("%s: %s\n", type_name(obj->type), oid_to_hex(&obj->oid));
+
+       ...
+}
+----
+
+NOTE: Since we will be examining this output directly as humans, we'll use
+`trace_printf()` here. Additionally, since this change introduces a significant
+number of printed lines, using `trace_printf()` will allow us to easily silence
+those lines without having to recompile.
+
+(Leave the counter increment logic in place.)
+
+With only that change, run again (but save yourself some scrollback):
+
+----
+$ GIT_TRACE=1 ./bin-wrappers/git walken | head -n 10
+----
+
+Take a look at the top commit with `git show` and the object ID you printed; it
+should be the same as the output of `git show HEAD`.
+
+Next, let's change a setting on our `struct rev_info` within
+`walken_object_walk()`. Find where you're changing the other settings on `rev`,
+such as `rev->tree_objects` and `rev->tree_blobs_in_commit_order`, and add the
+`reverse` setting at the bottom:
+
+----
+       ...
+
+       rev->tree_objects = 1;
+       rev->blob_objects = 1;
+       rev->tag_objects = 1;
+       rev->tree_blobs_in_commit_order = 1;
+       rev->reverse = 1;
+
+       ...
+----
+
+Now, run again, but this time, let's grab the last handful of objects instead
+of the first handful:
+
+----
+$ make
+$ GIT_TRACE=1 ./bin-wrappers git walken | tail -n 10
+----
+
+The last commit object given should have the same OID as the one we saw at the
+top before, and running `git show <oid>` with that OID should give you again
+the same results as `git show HEAD`. Furthermore, if you run and examine the
+first ten lines again (with `head` instead of `tail` like we did before applying
+the `reverse` setting), you should see that now the first commit printed is the
+initial commit, `e83c5163`.
+
+== Wrapping Up
+
+Let's review. In this tutorial, we:
+
+- Built a commit walk from the ground up
+- Enabled a grep filter for that commit walk
+- Changed the sort order of that filtered commit walk
+- Built an object walk (tags, commits, trees, and blobs) from the ground up
+- Learned how to add a filter-spec to an object walk
+- Changed the display order of the filtered object walk
index bda29d2..bde1541 100644 (file)
@@ -6,10 +6,9 @@ Updates since v2.23
 
 Backward compatibility note
 
- * Although it is not officially deprecated, "filter-branch" is
-   showing its age and alternatives are available.  From this release,
-   we started to discourage its uses and hint people about
-   filter-repo.
+ * "filter-branch" is showing its age and alternatives are available.
+   From this release, we started to discourage its use and hint
+   people about filter-repo.
 
 UI, Workflows & Features
 
@@ -71,6 +70,13 @@ UI, Workflows & Features
  * "git add -i" has been taught to show the total number of hunks and
    the hunks that has been processed so far when showing prompts.
 
+ * "git fetch --jobs=<n>" allowed <n> parallel jobs when fetching
+   submodules, but this did not apply to "git fetch --multiple" that
+   fetches from multiple remote repositories.  It now does.
+
+ * The installation instruction for zsh completion script (in
+   contrib/) has been a bit improved.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -128,6 +134,18 @@ Performance, Internal Implementation, Development Support etc.
 
  * Preparation for SHA-256 upgrade continues.
 
+ * "git log --graph" for an octopus merge is sometimes colored
+   incorrectly, which is demonstrated and documented but not yet
+   fixed.
+
+ * The trace2 output, when sending them to files in a designated
+   directory, can populate the directory with too many files; a
+   mechanism is introduced to set the maximum number of files and
+   discard further logs when the maximum is reached.
+
+ * We have adopted a Code-of-conduct document.
+   (merge 3f9ef874a7 jk/coc later to maint).
+
 
 Fixes since v2.23
 -----------------
@@ -297,6 +315,34 @@ Fixes since v2.23
    to access the worktree correctly, which has been corrected.
    (merge dfd557c978 js/stash-apply-in-secondary-worktree later to maint).
 
+ * The merge-recursive machinery is one of the most complex parts of
+   the system that accumulated cruft over time.  This large series
+   cleans up the implementation quite a bit.
+   (merge b657047719 en/merge-recursive-cleanup later to maint).
+
+ * Pretty-printed command line formatter (used in e.g. reporting the
+   command being run by the tracing API) had a bug that lost an
+   argument that is an empty string, which has been corrected.
+   (merge ce2d7ed2fd gs/sq-quote-buf-pretty later to maint).
+
+ * "git range-diff" failed to handle mode-only change, which has been
+   corrected.
+   (merge 2b6a9b13ca tg/range-diff-output-update later to maint).
+
+ * Dev support update.
+   (merge 4f3c1dc5d6 dl/allow-running-cocci-verbosely later to maint).
+
+ * "git format-patch -o <outdir>" did an equivalent of "mkdir <outdir>"
+   not "mkdir -p <outdir>", which was corrected.
+
+ * "git stash save" lost local changes to submodules, which has been
+   corrected.
+   (merge 556895d0c8 jj/stash-reset-only-toplevel later to maint).
+
+ * The atomic push over smart HTTP transport did not work, which has
+   been corrected.
+   (merge 6f1194246a bc/smart-http-atomic-push later to maint).
+
  * Other code cleanup, docfix, build fix, etc.
    (merge d1387d3895 en/fast-import-merge-doc later to maint).
    (merge 1c24a54ea4 bm/repository-layout-typofix later to maint).
@@ -335,3 +381,18 @@ Fixes since v2.23
    (merge 53d687bf5f ah/cleanups later to maint).
    (merge f537485fa5 rs/test-remove-useless-debugging-cat later to maint).
    (merge 11a3d3aadd dl/rev-list-doc-cleanup later to maint).
+   (merge d928a8388a am/t0028-utf16-tests later to maint).
+   (merge b05b40930e dl/t0000-skip-test-test later to maint).
+   (merge 03d3b1297c js/xdiffi-comment-updates later to maint).
+   (merge 57d8f4b4c7 js/doc-stash-save later to maint).
+   (merge 8c1cfd58e3 ta/t1308-typofix later to maint).
+   (merge fa364ad790 bb/utf8-wcwidth-cleanup later to maint).
+   (merge 68b69211b2 bb/compat-util-comment-fix later to maint).
+   (merge 5cc6a4be11 rs/http-push-simplify later to maint).
+   (merge a81e42d235 rs/column-use-utf8-strnwidth later to maint).
+   (merge 062a309d36 rs/remote-curl-use-argv-array later to maint).
+   (merge 3b3c79f6c9 nr/diff-highlight-indent-fix later to maint).
+   (merge 3444ec2eb2 wb/fsmonitor-bitmap-fix later to maint).
+   (merge 10da030ab7 cb/pcre2-chartables-leakfix later to maint).
+   (merge 60e6569a12 js/mingw-needs-hiding-fix later to maint).
+   (merge 52bd3e4657 rl/gitweb-blame-prev-fix later to maint).
index e8cb205..f119402 100644 (file)
@@ -70,6 +70,16 @@ fetch.showForcedUpdates::
        linkgit:git-fetch[1] and linkgit:git-pull[1] commands.
        Defaults to true.
 
+fetch.parallel::
+       Specifies the maximal number of fetch operations to be run in parallel
+       at a time (submodules, or remotes when the `--multiple` option of
+       linkgit:git-fetch[1] is in effect).
++
+A value of 0 will give some reasonable default. If unset, it defaults to 1.
++
+For submodules, this setting can be overridden using the `submodule.fetchJobs`
+config setting.
+
 fetch.writeCommitGraph::
        Set to true to write a commit-graph after every `git fetch` command
        that downloads a pack-file from a remote. Using the `--split` option,
index cb629fa..513fcd8 100644 (file)
@@ -36,6 +36,12 @@ format.subjectPrefix::
        The default for format-patch is to output files with the '[PATCH]'
        subject prefix. Use this variable to change that prefix.
 
+format.coverFromDescription::
+       The default mode for format-patch to determine which parts of
+       the cover letter will be populated using the branch's
+       description. See the `--cover-from-description` option in
+       linkgit:git-format-patch[1].
+
 format.signature::
        The default for format-patch is to output a signature containing
        the Git version number. Use this variable to change that default.
@@ -81,7 +87,7 @@ format.coverLetter::
 
 format.outputDirectory::
        Set a custom directory to store the resulting files instead of the
-       current working directory.
+       current working directory. All directory components will be created.
 
 format.useAutoBase::
        A boolean value which lets you enable the `--base=auto` option of
index 2edbfb0..4ce0b9a 100644 (file)
@@ -54,3 +54,9 @@ trace2.destinationDebug::
        By default, these errors are suppressed and tracing is
        silently disabled.  May be overridden by the
        `GIT_TRACE2_DST_DEBUG` environment variable.
+
+trace2.maxFiles::
+       Integer.  When writing trace files to a target directory, do not
+       write additional traces if we would exceed this many files. Instead,
+       write a sentinel file that will block further tracing to this
+       directory. Defaults to 0, which disables this check.
index 99df1f3..43b9ff3 100644 (file)
@@ -160,10 +160,15 @@ ifndef::git-pull[]
 
 -j::
 --jobs=<n>::
-       Number of parallel children to be used for fetching submodules.
-       Each will fetch from different submodules, such that fetching many
-       submodules will be faster. By default submodules will be fetched
-       one at a time.
+       Number of parallel children to be used for all forms of fetching.
++
+If the `--multiple` option was specified, the different remotes will be fetched
+in parallel. If multiple submodules are fetched, they will be fetched in
+parallel. To control them independently, use the config settings
+`fetch.parallel` and `submodule.fetchJobs` (see linkgit:git-config[1]).
++
+Typically, parallel recursive and multi-remote fetches will be faster. By
+default fetches are performed sequentially, not in parallel.
 
 --no-recurse-submodules::
        Disable recursive fetching of submodules (this has the same effect as
index 784e934..37634bf 100644 (file)
@@ -75,11 +75,20 @@ produced incorrect results if you gave these options.
        Before processing any input, load the marks specified in
        <file>.  The input file must exist, must be readable, and
        must use the same format as produced by --export-marks.
+
+--mark-tags::
+       In addition to labelling blobs and commits with mark ids, also
+       label tags.  This is useful in conjunction with
+       `--export-marks` and `--import-marks`, and is also useful (and
+       necessary) for exporting of nested tags.  It does not hurt
+       other cases and would be the default, but many fast-import
+       frontends are not prepared to accept tags with mark
+       identifiers.
 +
-Any commits that have already been marked will not be exported again.
-If the backend uses a similar --import-marks file, this allows for
-incremental bidirectional exporting of the repository by keeping the
-marks the same across runs.
+Any commits (or tags) that have already been marked will not be
+exported again.  If the backend uses a similar --import-marks file,
+this allows for incremental bidirectional exporting of the repository
+by keeping the marks the same across runs.
 
 --fake-missing-tagger::
        Some old repositories have tags without a tagger.  The
index 0bb2762..a3f1e0c 100644 (file)
@@ -337,6 +337,13 @@ and control the current import process.  More detailed discussion
        `commit` command.  This command is optional and is not
        needed to perform an import.
 
+`alias`::
+       Record that a mark refers to a given object without first
+       creating any new object.  Using --import-marks and referring
+       to missing marks will cause fast-import to fail, so aliases
+       can provide a way to set otherwise pruned commits to a valid
+       value (e.g. the nearest non-pruned ancestor).
+
 `checkpoint`::
        Forces fast-import to close the current packfile, generate its
        unique SHA-1 checksum and index, and start a new packfile.
@@ -774,6 +781,7 @@ lightweight (non-annotated) tags see the `reset` command below.
 
 ....
        'tag' SP <name> LF
+       mark?
        'from' SP <commit-ish> LF
        original-oid?
        'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
@@ -913,6 +921,21 @@ a data chunk which does not have an LF as its last byte.
 +
 The `LF` after `<delim> LF` is optional (it used to be required).
 
+`alias`
+~~~~~~~
+Record that a mark refers to a given object without first creating any
+new object.
+
+....
+       'alias' LF
+       mark
+       'to' SP <commit-ish> LF
+       LF?
+....
+
+For a detailed description of `<commit-ish>` see above under `from`.
+
+
 `checkpoint`
 ~~~~~~~~~~~~
 Forces fast-import to close the current packfile, start a new one, and to
index 0ac56f4..00bdf9b 100644 (file)
@@ -19,6 +19,7 @@ SYNOPSIS
                   [--start-number <n>] [--numbered-files]
                   [--in-reply-to=<message id>] [--suffix=.<sfx>]
                   [--ignore-if-in-upstream]
+                  [--cover-from-description=<mode>]
                   [--rfc] [--subject-prefix=<subject prefix>]
                   [(--reroll-count|-v) <n>]
                   [--to=<email>] [--cc=<email>]
@@ -66,7 +67,8 @@ they are created in the current working directory. The default path
 can be set with the `format.outputDirectory` configuration option.
 The `-o` option takes precedence over `format.outputDirectory`.
 To store patches in the current working directory even when
-`format.outputDirectory` points elsewhere, use `-o .`.
+`format.outputDirectory` points elsewhere, use `-o .`. All directory
+components will be created.
 
 By default, the subject of a single patch is "[PATCH] " followed by
 the concatenation of lines from the commit message up to the first blank
@@ -171,6 +173,26 @@ will want to ensure that threading is disabled for `git send-email`.
        patches being generated, and any patch that matches is
        ignored.
 
+--cover-from-description=<mode>::
+       Controls which parts of the cover letter will be automatically
+       populated using the branch's description.
++
+If `<mode>` is `message` or `default`, the cover letter subject will be
+populated with placeholder text. The body of the cover letter will be
+populated with the branch's description. This is the default mode when
+no configuration nor command line option is specified.
++
+If `<mode>` is `subject`, the first paragraph of the branch description will
+populate the cover letter subject. The remainder of the description will
+populate the body of the cover letter.
++
+If `<mode>` is `auto`, if the first paragraph of the branch description
+is greater than 100 bytes, then the mode will be `message`, otherwise
+`subject` will be used.
++
+If `<mode>` is `none`, both the cover letter subject and body will be
+populated with placeholder text.
+
 --subject-prefix=<subject prefix>::
        Instead of the standard '[PATCH]' prefix in the subject
        line, instead use '[<subject prefix>]'. This
@@ -347,6 +369,7 @@ with configuration variables.
        signOff = true
        outputDirectory = <directory>
        coverLetter = auto
+       coverFromDescription = auto
 ------------
 
 
index f56a5a9..ced2e82 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 [verse]
 'git notes' [list [<object>]]
 'git notes' add [-f] [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
-'git notes' copy [-f] ( --stdin | <from-object> <to-object> )
+'git notes' copy [-f] ( --stdin | <from-object> [<to-object>] )
 'git notes' append [--allow-empty] [-F <file> | -m <msg> | (-c | -C) <object>] [<object>]
 'git notes' edit [--allow-empty] [<object>]
 'git notes' show [<object>]
@@ -68,8 +68,8 @@ add::
        subcommand).
 
 copy::
-       Copy the notes for the first object onto the second object.
-       Abort if the second object already has notes, or if the first
+       Copy the notes for the first object onto the second object (defaults to
+       HEAD). Abort if the second object already has notes, or if the first
        object has none (use -f to overwrite existing notes to the
        second object). This subcommand is equivalent to:
        `git notes add [-f] -C $(git notes list <from-object>) <to-object>`
index 8fbe12c..53e1a12 100644 (file)
@@ -87,8 +87,9 @@ The `--patch` option implies `--keep-index`.  You can use
 save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]::
 
        This option is deprecated in favour of 'git stash push'.  It
-       differs from "stash push" in that it cannot take pathspecs,
-       and any non-option arguments form the message.
+       differs from "stash push" in that it cannot take pathspecs.
+       Instead, all non-option arguments are concatenated to form the stash
+       message.
 
 list [<options>]::
 
index a5c3c04..f48a031 100644 (file)
@@ -509,6 +509,11 @@ set by Git if the remote helper has the 'option' capability.
        Indicate that only the objects wanted need to be fetched, not
        their dependents.
 
+'option atomic' {'true'|'false'}::
+       When pushing, request the remote server to update refs in a single atomic
+       transaction.  If successful, all refs will be updated, or none will.  If the
+       remote side does not support this capability, the push will fail.
+
 SEE ALSO
 --------
 linkgit:git-remote[1]
index 608eb5d..94d6c1b 100644 (file)
@@ -1,12 +1,13 @@
 <!-- manpage-bold-literal.xsl:
      special formatting for manpages rendered from asciidoc+docbook -->
 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               xmlns:d="http://docbook.org/ns/docbook"
                version="1.0">
 
 <!-- render literal text as bold (instead of plain or monospace);
      this makes literal text easier to distinguish in manpages
      viewed on a tty -->
-<xsl:template match="literal">
+<xsl:template match="literal|d:literal">
        <xsl:value-of select="$git.docbook.backslash"/>
        <xsl:text>fB</xsl:text>
        <xsl:apply-templates/>
index 71eb081..a045dbe 100644 (file)
@@ -128,7 +128,7 @@ yields
 
 ------------
 $ cat ~/log.event
-{"event":"version","sid":"sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.620713Z","file":"common-main.c","line":38,"evt":"1","exe":"2.20.1.155.g426c96fcdb"}
+{"event":"version","sid":"sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.620713Z","file":"common-main.c","line":38,"evt":"2","exe":"2.20.1.155.g426c96fcdb"}
 {"event":"start","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621027Z","file":"common-main.c","line":39,"t_abs":0.001173,"argv":["git","version"]}
 {"event":"cmd_name","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621122Z","file":"git.c","line":432,"name":"version","hierarchy":"version"}
 {"event":"exit","sid":"20190408T191610.507018Z-H9b68c35f-P000059a8","thread":"main","time":"2019-01-16T17:28:42.621236Z","file":"git.c","line":662,"t_abs":0.001227,"code":0}
@@ -142,10 +142,9 @@ system or global config value to one of the following:
 
 include::../trace2-target-values.txt[]
 
-If the target already exists and is a directory, the traces will be
-written to files (one per process) underneath the given directory. They
-will be named according to the last component of the SID (optionally
-followed by a counter to avoid filename collisions).
+When trace files are written to a target directory, they will be named according
+to the last component of the SID (optionally followed by a counter to avoid
+filename collisions).
 
 == Trace2 API
 
@@ -605,17 +604,35 @@ only present on the "start" and "atexit" events.
 ==== Event-Specific Key/Value Pairs
 
 `"version"`::
-       This event gives the version of the executable and the EVENT format.
+       This event gives the version of the executable and the EVENT format. It
+       should always be the first event in a trace session. The EVENT format
+       version will be incremented if new event types are added, if existing
+       fields are removed, or if there are significant changes in
+       interpretation of existing events or fields. Smaller changes, such as
+       adding a new field to an existing event, will not require an increment
+       to the EVENT format version.
 +
 ------------
 {
        "event":"version",
        ...
-       "evt":"1",                     # EVENT format version
+       "evt":"2",                     # EVENT format version
        "exe":"2.20.1.155.g426c96fcdb" # git version
 }
 ------------
 
+`"discard"`::
+       This event is written to the git-trace2-discard sentinel file if there
+       are too many files in the target trace directory (see the
+       trace2.maxFiles config option).
++
+------------
+{
+       "event":"discard",
+       ...
+}
+------------
+
 `"start"`::
        This event contains the complete argv received by main().
 +
index 27d3c64..3985b6d 100644 (file)
@@ -2,7 +2,9 @@
 * `0` or `false` - Disables the target.
 * `1` or `true` - Writes to `STDERR`.
 * `[2-9]` - Writes to the already opened file descriptor.
-* `<absolute-pathname>` - Writes to the file in append mode.
+* `<absolute-pathname>` - Writes to the file in append mode. If the target
+already exists and is a directory, the traces will be written to files (one
+per process) underneath the given directory.
 * `af_unix:[<socket_type>:]<absolute-pathname>` - Write to a
 Unix DomainSocket (on platforms that support them).  Socket
 type can be either `stream` or `dgram`; if omitted Git will
index 98f88a2..5048d9b 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.23.GIT
+DEF_VER=v2.24.0
 
 LF='
 '
index 03b800d..58b92af 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1885,6 +1885,7 @@ ifndef V
        QUIET_SP       = @echo '   ' SP $<;
        QUIET_HDR      = @echo '   ' HDR $(<:hcc=h);
        QUIET_RC       = @echo '   ' RC $@;
+       QUIET_SPATCH   = @echo '   ' SPATCH $<;
        QUIET_SUBDIR0  = +@subdir=
        QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
                         $(MAKE) $(PRINT_DIR) -C $$subdir
@@ -2813,7 +2814,7 @@ FOUND_C_SOURCES = $(filter %.c,$(shell $(FIND_SOURCE_FILES)))
 COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES))
 
 %.cocci.patch: %.cocci $(COCCI_SOURCES)
-       @echo '    ' SPATCH $<; \
+       $(QUIET_SPATCH) \
        if test $(SPATCH_BATCH_SIZE) = 0; then \
                limit=; \
        else \
@@ -3042,6 +3043,10 @@ rpm::
        @false
 .PHONY: rpm
 
+ifneq ($(INCLUDE_DLLS_IN_ARTIFACTS),)
+OTHER_PROGRAMS += $(shell echo *.dll t/helper/*.dll)
+endif
+
 artifacts-tar:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) \
                GIT-BUILD-OPTIONS $(TEST_PROGRAMS) $(test_bindir_programs) \
                $(MOFILES)
diff --git a/apply.c b/apply.c
index 57a61f2..f8a046a 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -1361,11 +1361,32 @@ int parse_git_diff_header(struct strbuf *root,
                        if (check_header_line(*linenr, patch))
                                return -1;
                        if (res > 0)
-                               return offset;
+                               goto done;
                        break;
                }
        }
 
+done:
+       if (!patch->old_name && !patch->new_name) {
+               if (!patch->def_name) {
+                       error(Q_("git diff header lacks filename information when removing "
+                                "%d leading pathname component (line %d)",
+                                "git diff header lacks filename information when removing "
+                                "%d leading pathname components (line %d)",
+                                parse_hdr_state.p_value),
+                             parse_hdr_state.p_value, *linenr);
+                       return -128;
+               }
+               patch->old_name = xstrdup(patch->def_name);
+               patch->new_name = xstrdup(patch->def_name);
+       }
+       if ((!patch->new_name && !patch->is_delete) ||
+           (!patch->old_name && !patch->is_new)) {
+               error(_("git diff header lacks filename information "
+                       "(line %d)"), *linenr);
+               return -128;
+       }
+       patch->is_toplevel_relative = 1;
        return offset;
 }
 
@@ -1546,26 +1567,6 @@ static int find_header(struct apply_state *state,
                                return -128;
                        if (git_hdr_len <= len)
                                continue;
-                       if (!patch->old_name && !patch->new_name) {
-                               if (!patch->def_name) {
-                                       error(Q_("git diff header lacks filename information when removing "
-                                                       "%d leading pathname component (line %d)",
-                                                       "git diff header lacks filename information when removing "
-                                                       "%d leading pathname components (line %d)",
-                                                       state->p_value),
-                                                    state->p_value, state->linenr);
-                                       return -128;
-                               }
-                               patch->old_name = xstrdup(patch->def_name);
-                               patch->new_name = xstrdup(patch->def_name);
-                       }
-                       if ((!patch->new_name && !patch->is_delete) ||
-                           (!patch->old_name && !patch->is_new)) {
-                               error(_("git diff header lacks filename information "
-                                            "(line %d)"), state->linenr);
-                               return -128;
-                       }
-                       patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
                }
diff --git a/attr.c b/attr.c
index d02d081..11f19b5 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -62,7 +62,7 @@ static struct attr_hashmap g_attr_hashmap;
 
 /* The container for objects stored in "struct attr_hashmap" */
 struct attr_hash_entry {
-       struct hashmap_entry ent; /* must be the first member! */
+       struct hashmap_entry ent;
        const char *key; /* the key; memory should be owned by value */
        size_t keylen; /* length of the key */
        void *value; /* the stored value */
@@ -70,12 +70,14 @@ struct attr_hash_entry {
 
 /* attr_hashmap comparison function */
 static int attr_hash_entry_cmp(const void *unused_cmp_data,
-                              const void *entry,
-                              const void *entry_or_key,
+                              const struct hashmap_entry *eptr,
+                              const struct hashmap_entry *entry_or_key,
                               const void *unused_keydata)
 {
-       const struct attr_hash_entry *a = entry;
-       const struct attr_hash_entry *b = entry_or_key;
+       const struct attr_hash_entry *a, *b;
+
+       a = container_of(eptr, const struct attr_hash_entry, ent);
+       b = container_of(entry_or_key, const struct attr_hash_entry, ent);
        return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen);
 }
 
@@ -98,10 +100,10 @@ static void *attr_hashmap_get(struct attr_hashmap *map,
        if (!map->map.tablesize)
                attr_hashmap_init(map);
 
-       hashmap_entry_init(&k, memhash(key, keylen));
+       hashmap_entry_init(&k.ent, memhash(key, keylen));
        k.key = key;
        k.keylen = keylen;
-       e = hashmap_get(&map->map, &k, NULL);
+       e = hashmap_get_entry(&map->map, &k, ent, NULL);
 
        return e ? e->value : NULL;
 }
@@ -117,12 +119,12 @@ static void attr_hashmap_add(struct attr_hashmap *map,
                attr_hashmap_init(map);
 
        e = xmalloc(sizeof(struct attr_hash_entry));
-       hashmap_entry_init(e, memhash(key, keylen));
+       hashmap_entry_init(&e->ent, memhash(key, keylen));
        e->key = key;
        e->keylen = keylen;
        e->value = value;
 
-       hashmap_add(&map->map, e);
+       hashmap_add(&map->map, &e->ent);
 }
 
 struct all_attrs_item {
@@ -161,12 +163,12 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
        if (size != check->all_attrs_nr) {
                struct attr_hash_entry *e;
                struct hashmap_iter iter;
-               hashmap_iter_init(&map->map, &iter);
 
                REALLOC_ARRAY(check->all_attrs, size);
                check->all_attrs_nr = size;
 
-               while ((e = hashmap_iter_next(&iter))) {
+               hashmap_for_each_entry(&map->map, &iter, e,
+                                       ent /* member name */) {
                        const struct git_attr *a = e->value;
                        check->all_attrs[a->attr_nr].attr = a;
                }
index cb9ab1e..af2a5ea 100644 (file)
@@ -1,6 +1,5 @@
-resources:
-- repo: self
-  fetchDepth: 1
+variables:
+  Agent.Source.Git.ShallowFetchDepth: 1
 
 jobs:
 - job: windows_build
@@ -131,6 +130,165 @@ jobs:
       PathtoPublish: t/failed-test-artifacts
       ArtifactName: failed-test-artifacts
 
+- job: vs_build
+  displayName: Visual Studio Build
+  condition: succeeded()
+  pool: Hosted VS2017
+  timeoutInMinutes: 240
+  steps:
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
+        cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
+      }
+    displayName: 'Mount test-cache'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - powershell: |
+      $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds"
+      $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
+      $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl
+      (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip")
+      Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force
+      Remove-Item git-sdk-64-minimal.zip
+
+      # Let Git ignore the SDK and the test-cache
+      "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude"
+    displayName: 'Download git-sdk-64-minimal'
+  - powershell: |
+      & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+        make NDEBUG=1 DEVELOPER=1 vcxproj
+      "@
+      if (!$?) { exit(1) }
+    displayName: Generate Visual Studio Solution
+    env:
+      HOME: $(Build.SourcesDirectory)
+      MSYSTEM: MINGW64
+      DEVELOPER: 1
+      NO_PERL: 1
+      GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'"
+  - powershell: |
+      $urlbase = "https://dev.azure.com/git/git/_apis/build/builds"
+      $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id
+      $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl
+      (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip")
+      Expand-Archive compat.zip -DestinationPath . -Force
+      Remove-Item compat.zip
+    displayName: 'Download vcpkg artifacts'
+  - task: MSBuild@1
+    inputs:
+      solution: git.sln
+      platform: x64
+      configuration: Release
+      maximumCpuCount: 4
+  - powershell: |
+      & compat\vcbuild\vcpkg_copy_dlls.bat release
+      if (!$?) { exit(1) }
+      & git-sdk-64-minimal\usr\bin\bash.exe -lc @"
+        mkdir -p artifacts &&
+        eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)\"
+      "@
+      if (!$?) { exit(1) }
+    displayName: Bundle artifact tar
+    env:
+      HOME: $(Build.SourcesDirectory)
+      MSYSTEM: MINGW64
+      DEVELOPER: 1
+      NO_PERL: 1
+      MSVC: 1
+      VCPKG_ROOT: $(Build.SourcesDirectory)\compat\vcbuild\vcpkg
+  - powershell: |
+      $tag = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-tag.txt").content
+      $version = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-version.txt").content
+      $url = "https://github.com/git-for-windows/git/releases/download/${tag}/PortableGit-${version}-64-bit.7z.exe"
+      (New-Object Net.WebClient).DownloadFile($url,"PortableGit.exe")
+      & .\PortableGit.exe -y -oartifacts\PortableGit
+      # Wait until it is unpacked
+      while (-not @(Remove-Item -ErrorAction SilentlyContinue PortableGit.exe; $?)) { sleep 1 }
+    displayName: Download & extract portable Git
+  - task: PublishPipelineArtifact@0
+    displayName: 'Publish Pipeline Artifact: MSVC test artifacts'
+    inputs:
+      artifactName: 'vs-artifacts'
+      targetPath: '$(Build.SourcesDirectory)\artifacts'
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
+      }
+    displayName: 'Unmount test-cache'
+    condition: true
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+
+- job: vs_test
+  displayName: Visual Studio Test
+  dependsOn: vs_build
+  condition: succeeded()
+  pool: Hosted
+  timeoutInMinutes: 240
+  strategy:
+    parallel: 10
+  steps:
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no
+        cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\
+      }
+    displayName: 'Mount test-cache'
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: DownloadPipelineArtifact@0
+    displayName: 'Download Pipeline Artifact: VS test artifacts'
+    inputs:
+      artifactName: 'vs-artifacts'
+      targetPath: '$(Build.SourcesDirectory)'
+  - powershell: |
+      & PortableGit\git-cmd.exe --command=usr\bin\bash.exe -lc @"
+        test -f artifacts.tar.gz || {
+          echo No test artifacts found\; skipping >&2
+          exit 0
+        }
+        tar xf artifacts.tar.gz || exit 1
+
+        # Let Git ignore the SDK and the test-cache
+        printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude
+
+        cd t &&
+        PATH=\"`$PWD/helper:`$PATH\" &&
+        test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \
+                `$(test-tool.exe path-utils slice-tests \
+                        `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh)
+      "@
+      if (!$?) { exit(1) }
+    displayName: 'Test (parallel)'
+    env:
+      HOME: $(Build.SourcesDirectory)
+      MSYSTEM: MINGW64
+      NO_SVN_TESTS: 1
+      GIT_TEST_SKIP_REBASE_P: 1
+  - powershell: |
+      if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") {
+        cmd /c rmdir "$(Build.SourcesDirectory)\test-cache"
+      }
+    displayName: 'Unmount test-cache'
+    condition: true
+    env:
+      GITFILESHAREPWD: $(gitfileshare.pwd)
+  - task: PublishTestResults@2
+    displayName: 'Publish Test Results **/TEST-*.xml'
+    inputs:
+      mergeTestResults: true
+      testRunTitle: 'vs'
+      platform: Windows
+      publishRunAttachments: false
+    condition: succeededOrFailed()
+  - task: PublishBuildArtifacts@1
+    displayName: 'Publish trash directories of failed tests'
+    condition: failed()
+    inputs:
+      PathtoPublish: t/failed-test-artifacts
+      ArtifactName: failed-vs-test-artifacts
+
 - job: linux_clang
   displayName: linux-clang
   condition: succeeded()
diff --git a/blame.c b/blame.c
index 6596d8d..29770e5 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -417,14 +417,15 @@ static void get_fingerprint(struct fingerprint *result,
                /* Ignore whitespace pairs */
                if (hash == 0)
                        continue;
-               hashmap_entry_init(entry, hash);
+               hashmap_entry_init(&entry->entry, hash);
 
-               found_entry = hashmap_get(&result->map, entry, NULL);
+               found_entry = hashmap_get_entry(&result->map, entry,
+                                               /* member name */ entry, NULL);
                if (found_entry) {
                        found_entry->count += 1;
                } else {
                        entry->count = 1;
-                       hashmap_add(&result->map, entry);
+                       hashmap_add(&result->map, &entry->entry);
                        ++entry;
                }
        }
@@ -432,7 +433,7 @@ static void get_fingerprint(struct fingerprint *result,
 
 static void free_fingerprint(struct fingerprint *f)
 {
-       hashmap_free(&f->map, 0);
+       hashmap_free(&f->map);
        free(f->entries);
 }
 
@@ -449,10 +450,10 @@ static int fingerprint_similarity(struct fingerprint *a, struct fingerprint *b)
        struct hashmap_iter iter;
        const struct fingerprint_entry *entry_a, *entry_b;
 
-       hashmap_iter_init(&b->map, &iter);
-
-       while ((entry_b = hashmap_iter_next(&iter))) {
-               if ((entry_a = hashmap_get(&a->map, entry_b, NULL))) {
+       hashmap_for_each_entry(&b->map, &iter, entry_b,
+                               entry /* member name */) {
+               entry_a = hashmap_get_entry(&a->map, entry_b, entry, NULL);
+               if (entry_a) {
                        intersection += entry_a->count < entry_b->count ?
                                        entry_a->count : entry_b->count;
                }
@@ -470,10 +471,12 @@ static void fingerprint_subtract(struct fingerprint *a, struct fingerprint *b)
 
        hashmap_iter_init(&b->map, &iter);
 
-       while ((entry_b = hashmap_iter_next(&iter))) {
-               if ((entry_a = hashmap_get(&a->map, entry_b, NULL))) {
+       hashmap_for_each_entry(&b->map, &iter, entry_b,
+                               entry /* member name */) {
+               entry_a = hashmap_get_entry(&a->map, entry_b, entry, NULL);
+               if (entry_a) {
                        if (entry_a->count <= entry_b->count)
-                               hashmap_remove(&a->map, entry_b, NULL);
+                               hashmap_remove(&a->map, &entry_b->entry, NULL);
                        else
                                entry_a->count -= entry_b->count;
                }
index 761cac3..8181c2a 100644 (file)
@@ -1526,7 +1526,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
        o.branch1 = "HEAD";
        their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
        o.branch2 = their_tree_name;
-       o.detect_directory_renames = 0;
+       o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE;
 
        if (state->quiet)
                o.verbosity = 0;
index e946ba6..10185cc 100644 (file)
@@ -319,18 +319,18 @@ static const char *format_time(timestamp_t time, const char *tz_str,
        return time_buf.buf;
 }
 
-#define OUTPUT_ANNOTATE_COMPAT 001
-#define OUTPUT_LONG_OBJECT_NAME        002
-#define OUTPUT_RAW_TIMESTAMP   004
-#define OUTPUT_PORCELAIN       010
-#define OUTPUT_SHOW_NAME       020
-#define OUTPUT_SHOW_NUMBER     040
-#define OUTPUT_SHOW_SCORE      0100
-#define OUTPUT_NO_AUTHOR       0200
-#define OUTPUT_SHOW_EMAIL      0400
-#define OUTPUT_LINE_PORCELAIN  01000
-#define OUTPUT_COLOR_LINE      02000
-#define OUTPUT_SHOW_AGE_WITH_COLOR     04000
+#define OUTPUT_ANNOTATE_COMPAT      (1U<<0)
+#define OUTPUT_LONG_OBJECT_NAME     (1U<<1)
+#define OUTPUT_RAW_TIMESTAMP        (1U<<2)
+#define OUTPUT_PORCELAIN            (1U<<3)
+#define OUTPUT_SHOW_NAME            (1U<<4)
+#define OUTPUT_SHOW_NUMBER          (1U<<5)
+#define OUTPUT_SHOW_SCORE           (1U<<6)
+#define OUTPUT_NO_AUTHOR            (1U<<7)
+#define OUTPUT_SHOW_EMAIL           (1U<<8)
+#define OUTPUT_LINE_PORCELAIN       (1U<<9)
+#define OUTPUT_COLOR_LINE           (1U<<10)
+#define OUTPUT_SHOW_AGE_WITH_COLOR  (1U<<11)
 
 static void emit_porcelain_details(struct blame_origin *suspect, int repeat)
 {
index 1283727..3634a3d 100644 (file)
@@ -709,11 +709,11 @@ static int merge_working_tree(const struct checkout_opts *opts,
                         * give up or do a real merge, depending on
                         * whether the merge flag was used.
                         */
-                       struct tree *result;
                        struct tree *work;
                        struct tree *old_tree;
                        struct merge_options o;
                        struct strbuf sb = STRBUF_INIT;
+                       struct strbuf old_commit_shortname = STRBUF_INIT;
 
                        if (!opts->merge)
                                return 1;
@@ -754,7 +754,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
                         */
                        init_merge_options(&o, the_repository);
                        o.verbosity = 0;
-                       work = write_tree_from_memory(&o);
+                       work = write_in_core_index_as_tree(the_repository);
 
                        ret = reset_tree(new_tree,
                                         opts, 1,
@@ -762,19 +762,25 @@ static int merge_working_tree(const struct checkout_opts *opts,
                        if (ret)
                                return ret;
                        o.ancestor = old_branch_info->name;
+                       if (old_branch_info->name == NULL) {
+                               strbuf_add_unique_abbrev(&old_commit_shortname,
+                                                        &old_branch_info->commit->object.oid,
+                                                        DEFAULT_ABBREV);
+                               o.ancestor = old_commit_shortname.buf;
+                       }
                        o.branch1 = new_branch_info->name;
                        o.branch2 = "local";
                        ret = merge_trees(&o,
                                          new_tree,
                                          work,
-                                         old_tree,
-                                         &result);
+                                         old_tree);
                        if (ret < 0)
                                exit(128);
                        ret = reset_tree(new_tree,
                                         opts, 0,
                                         writeout_error);
                        strbuf_release(&o.obuf);
+                       strbuf_release(&old_commit_shortname);
                        if (ret)
                                return ret;
                }
index a6c500d..addc8d4 100644 (file)
@@ -60,6 +60,8 @@ static int graph_verify(int argc, const char **argv)
                OPT_END(),
        };
 
+       trace2_cmd_mode("verify");
+
        opts.progress = isatty(2);
        argc = parse_options(argc, argv, NULL,
                             builtin_commit_graph_verify_options,
@@ -107,6 +109,8 @@ static int graph_read(int argc, const char **argv)
                OPT_END(),
        };
 
+       trace2_cmd_mode("read");
+
        argc = parse_options(argc, argv, NULL,
                             builtin_commit_graph_read_options,
                             builtin_commit_graph_read_usage, 0);
@@ -190,6 +194,8 @@ static int graph_write(int argc, const char **argv)
        split_opts.max_commits = 0;
        split_opts.expire_time = 0;
 
+       trace2_cmd_mode("write");
+
        argc = parse_options(argc, argv, NULL,
                             builtin_commit_graph_write_options,
                             builtin_commit_graph_write_usage, 0);
index e588bc6..294dc57 100644 (file)
@@ -1463,28 +1463,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
        return git_status_config(k, v, s);
 }
 
-int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
-{
-       struct argv_array hook_env = ARGV_ARRAY_INIT;
-       va_list args;
-       int ret;
-
-       argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
-
-       /*
-        * Let the hook know that no editor will be launched.
-        */
-       if (!editor_is_used)
-               argv_array_push(&hook_env, "GIT_EDITOR=:");
-
-       va_start(args, name);
-       ret = run_hook_ve(hook_env.argv,name, args);
-       va_end(args);
-       argv_array_clear(&hook_env);
-
-       return ret;
-}
-
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
        const char *argv_gc_auto[] = {"gc", "--auto", NULL};
index 90feab1..b6df81d 100644 (file)
@@ -63,19 +63,22 @@ static const char *prio_names[] = {
 };
 
 static int commit_name_neq(const void *unused_cmp_data,
-                          const void *entry,
-                          const void *entry_or_key,
+                          const struct hashmap_entry *eptr,
+                          const struct hashmap_entry *entry_or_key,
                           const void *peeled)
 {
-       const struct commit_name *cn1 = entry;
-       const struct commit_name *cn2 = entry_or_key;
+       const struct commit_name *cn1, *cn2;
+
+       cn1 = container_of(eptr, const struct commit_name, entry);
+       cn2 = container_of(entry_or_key, const struct commit_name, entry);
 
        return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled);
 }
 
 static inline struct commit_name *find_commit_name(const struct object_id *peeled)
 {
-       return hashmap_get_from_hash(&names, oidhash(peeled), peeled);
+       return hashmap_get_entry_from_hash(&names, oidhash(peeled), peeled,
+                                               struct commit_name, entry);
 }
 
 static int replace_name(struct commit_name *e,
@@ -122,8 +125,8 @@ static void add_to_known_names(const char *path,
                if (!e) {
                        e = xmalloc(sizeof(struct commit_name));
                        oidcpy(&e->peeled, peeled);
-                       hashmap_entry_init(e, oidhash(peeled));
-                       hashmap_add(&names, e);
+                       hashmap_entry_init(&e->entry, oidhash(peeled));
+                       hashmap_add(&names, &e->entry);
                        e->path = NULL;
                }
                e->tag = tag;
@@ -329,8 +332,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
                struct commit_name *n;
 
                init_commit_names(&commit_names);
-               n = hashmap_iter_first(&names, &iter);
-               for (; n; n = hashmap_iter_next(&iter)) {
+               hashmap_for_each_entry(&names, &iter, n,
+                                       entry /* member name */) {
                        c = lookup_commit_reference_gently(the_repository,
                                                           &n->peeled, 1);
                        if (c)
index 16eb8b7..c280e68 100644 (file)
@@ -125,12 +125,15 @@ struct working_tree_entry {
 };
 
 static int working_tree_entry_cmp(const void *unused_cmp_data,
-                                 const void *entry,
-                                 const void *entry_or_key,
+                                 const struct hashmap_entry *eptr,
+                                 const struct hashmap_entry *entry_or_key,
                                  const void *unused_keydata)
 {
-       const struct working_tree_entry *a = entry;
-       const struct working_tree_entry *b = entry_or_key;
+       const struct working_tree_entry *a, *b;
+
+       a = container_of(eptr, const struct working_tree_entry, entry);
+       b = container_of(entry_or_key, const struct working_tree_entry, entry);
+
        return strcmp(a->path, b->path);
 }
 
@@ -145,12 +148,14 @@ struct pair_entry {
 };
 
 static int pair_cmp(const void *unused_cmp_data,
-                   const void *entry,
-                   const void *entry_or_key,
+                   const struct hashmap_entry *eptr,
+                   const struct hashmap_entry *entry_or_key,
                    const void *unused_keydata)
 {
-       const struct pair_entry *a = entry;
-       const struct pair_entry *b = entry_or_key;
+       const struct pair_entry *a, *b;
+
+       a = container_of(eptr, const struct pair_entry, entry);
+       b = container_of(entry_or_key, const struct pair_entry, entry);
 
        return strcmp(a->path, b->path);
 }
@@ -161,14 +166,14 @@ static void add_left_or_right(struct hashmap *map, const char *path,
        struct pair_entry *e, *existing;
 
        FLEX_ALLOC_STR(e, path, path);
-       hashmap_entry_init(e, strhash(path));
-       existing = hashmap_get(map, e, NULL);
+       hashmap_entry_init(&e->entry, strhash(path));
+       existing = hashmap_get_entry(map, e, entry, NULL);
        if (existing) {
                free(e);
                e = existing;
        } else {
                e->left[0] = e->right[0] = '\0';
-               hashmap_add(map, e);
+               hashmap_add(map, &e->entry);
        }
        strlcpy(is_right ? e->right : e->left, content, PATH_MAX);
 }
@@ -179,12 +184,14 @@ struct path_entry {
 };
 
 static int path_entry_cmp(const void *unused_cmp_data,
-                         const void *entry,
-                         const void *entry_or_key,
+                         const struct hashmap_entry *eptr,
+                         const struct hashmap_entry *entry_or_key,
                          const void *key)
 {
-       const struct path_entry *a = entry;
-       const struct path_entry *b = entry_or_key;
+       const struct path_entry *a, *b;
+
+       a = container_of(eptr, const struct path_entry, entry);
+       b = container_of(entry_or_key, const struct path_entry, entry);
 
        return strcmp(a->path, key ? key : b->path);
 }
@@ -234,8 +241,8 @@ static void changed_files(struct hashmap *result, const char *index_path,
        while (!strbuf_getline_nul(&buf, fp)) {
                struct path_entry *entry;
                FLEX_ALLOC_STR(entry, path, buf.buf);
-               hashmap_entry_init(entry, strhash(buf.buf));
-               hashmap_add(result, entry);
+               hashmap_entry_init(&entry->entry, strhash(buf.buf));
+               hashmap_add(result, &entry->entry);
        }
        fclose(fp);
        if (finish_command(&diff_files))
@@ -461,12 +468,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
 
                        /* Avoid duplicate working_tree entries */
                        FLEX_ALLOC_STR(entry, path, dst_path);
-                       hashmap_entry_init(entry, strhash(dst_path));
-                       if (hashmap_get(&working_tree_dups, entry, NULL)) {
+                       hashmap_entry_init(&entry->entry, strhash(dst_path));
+                       if (hashmap_get(&working_tree_dups, &entry->entry,
+                                       NULL)) {
                                free(entry);
                                continue;
                        }
-                       hashmap_add(&working_tree_dups, entry);
+                       hashmap_add(&working_tree_dups, &entry->entry);
 
                        if (!use_wt_file(workdir, dst_path, &roid)) {
                                if (checkout_path(rmode, &roid, dst_path,
@@ -530,8 +538,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
         * temporary file to both the left and right directories to show the
         * change in the recorded SHA1 for the submodule.
         */
-       hashmap_iter_init(&submodules, &iter);
-       while ((entry = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(&submodules, &iter, entry,
+                               entry /* member name */) {
                if (*entry->left) {
                        add_path(&ldir, ldir_len, entry->path);
                        ensure_leading_directories(ldir.buf);
@@ -549,8 +557,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,
         * shows only the link itself, not the contents of the link target.
         * This loop replicates that behavior.
         */
-       hashmap_iter_init(&symlinks2, &iter);
-       while ((entry = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(&symlinks2, &iter, entry,
+                               entry /* member name */) {
                if (*entry->left) {
                        add_path(&ldir, ldir_len, entry->path);
                        ensure_leading_directories(ldir.buf);
index f541f55..dbec4df 100644 (file)
@@ -40,6 +40,7 @@ static int no_data;
 static int full_tree;
 static int reference_excluded_commits;
 static int show_original_ids;
+static int mark_tags;
 static struct string_list extra_refs = STRING_LIST_INIT_NODUP;
 static struct string_list tag_refs = STRING_LIST_INIT_NODUP;
 static struct refspec refspecs = REFSPEC_INIT_FETCH;
@@ -126,10 +127,15 @@ struct anonymized_entry {
 };
 
 static int anonymized_entry_cmp(const void *unused_cmp_data,
-                               const void *va, const void *vb,
+                               const struct hashmap_entry *eptr,
+                               const struct hashmap_entry *entry_or_key,
                                const void *unused_keydata)
 {
-       const struct anonymized_entry *a = va, *b = vb;
+       const struct anonymized_entry *a, *b;
+
+       a = container_of(eptr, const struct anonymized_entry, hash);
+       b = container_of(entry_or_key, const struct anonymized_entry, hash);
+
        return a->orig_len != b->orig_len ||
                memcmp(a->orig, b->orig, a->orig_len);
 }
@@ -148,10 +154,10 @@ static const void *anonymize_mem(struct hashmap *map,
        if (!map->cmpfn)
                hashmap_init(map, anonymized_entry_cmp, NULL, 0);
 
-       hashmap_entry_init(&key, memhash(orig, *len));
+       hashmap_entry_init(&key.hash, memhash(orig, *len));
        key.orig = orig;
        key.orig_len = *len;
-       ret = hashmap_get(map, &key, NULL);
+       ret = hashmap_get_entry(map, &key, hash, NULL);
 
        if (!ret) {
                ret = xmalloc(sizeof(*ret));
@@ -160,7 +166,7 @@ static const void *anonymize_mem(struct hashmap *map,
                ret->orig_len = *len;
                ret->anon = generate(orig, len);
                ret->anon_len = *len;
-               hashmap_put(map, ret);
+               hashmap_put(map, &ret->hash);
        }
 
        *len = ret->anon_len;
@@ -842,25 +848,40 @@ static void handle_tag(const char *name, struct tag *tag)
                        free(buf);
                        return;
                case REWRITE:
-                       if (tagged->type != OBJ_COMMIT) {
-                               die("tag %s tags unexported %s!",
-                                   oid_to_hex(&tag->object.oid),
-                                   type_name(tagged->type));
-                       }
-                       p = rewrite_commit((struct commit *)tagged);
-                       if (!p) {
-                               printf("reset %s\nfrom %s\n\n",
-                                      name, oid_to_hex(&null_oid));
-                               free(buf);
-                               return;
+                       if (tagged->type == OBJ_TAG && !mark_tags) {
+                               die(_("Error: Cannot export nested tags unless --mark-tags is specified."));
+                       } else if (tagged->type == OBJ_COMMIT) {
+                               p = rewrite_commit((struct commit *)tagged);
+                               if (!p) {
+                                       printf("reset %s\nfrom %s\n\n",
+                                              name, oid_to_hex(&null_oid));
+                                       free(buf);
+                                       return;
+                               }
+                               tagged_mark = get_object_mark(&p->object);
+                       } else {
+                               /* tagged->type is either OBJ_BLOB or OBJ_TAG */
+                               tagged_mark = get_object_mark(tagged);
                        }
-                       tagged_mark = get_object_mark(&p->object);
                }
        }
 
+       if (tagged->type == OBJ_TAG) {
+               printf("reset %s\nfrom %s\n\n",
+                      name, oid_to_hex(&null_oid));
+       }
        if (starts_with(name, "refs/tags/"))
                name += 10;
-       printf("tag %s\nfrom :%d\n", name, tagged_mark);
+       printf("tag %s\n", name);
+       if (mark_tags) {
+               mark_next_object(&tag->object);
+               printf("mark :%"PRIu32"\n", last_idnum);
+       }
+       if (tagged_mark)
+               printf("from :%d\n", tagged_mark);
+       else
+               printf("from %s\n", oid_to_hex(&tagged->oid));
+
        if (show_original_ids)
                printf("original-oid %s\n", oid_to_hex(&tag->object.oid));
        printf("%.*s%sdata %d\n%.*s\n",
@@ -1047,11 +1068,16 @@ static void export_marks(char *file)
                error("Unable to write marks file %s.", file);
 }
 
-static void import_marks(char *input_file)
+static void import_marks(char *input_file, int check_exists)
 {
        char line[512];
-       FILE *f = xfopen(input_file, "r");
+       FILE *f;
+       struct stat sb;
+
+       if (check_exists && stat(input_file, &sb))
+               return;
 
+       f = xfopen(input_file, "r");
        while (fgets(line, sizeof(line), f)) {
                uint32_t mark;
                char *line_end, *mark_end;
@@ -1115,7 +1141,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        struct rev_info revs;
        struct object_array commits = OBJECT_ARRAY_INIT;
        struct commit *commit;
-       char *export_filename = NULL, *import_filename = NULL;
+       char *export_filename = NULL,
+            *import_filename = NULL,
+            *import_filename_if_exists = NULL;
        uint32_t lastimportid;
        struct string_list refspecs_list = STRING_LIST_INIT_NODUP;
        struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP;
@@ -1135,6 +1163,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                             N_("Dump marks to this file")),
                OPT_STRING(0, "import-marks", &import_filename, N_("file"),
                             N_("Import marks from this file")),
+               OPT_STRING(0, "import-marks-if-exists",
+                            &import_filename_if_exists,
+                            N_("file"),
+                            N_("Import marks from this file if it exists")),
                OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger,
                         N_("Fake a tagger when tags lack one")),
                OPT_BOOL(0, "full-tree", &full_tree,
@@ -1149,6 +1181,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
                         &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")),
                OPT_BOOL(0, "show-original-ids", &show_original_ids,
                            N_("Show original object ids of blobs/commits")),
+               OPT_BOOL(0, "mark-tags", &mark_tags,
+                           N_("Label tags with mark ids")),
 
                OPT_END()
        };
@@ -1182,8 +1216,12 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
        if (use_done_feature)
                printf("feature done\n");
 
+       if (import_filename && import_filename_if_exists)
+               die(_("Cannot pass both --import-marks and --import-marks-if-exists"));
        if (import_filename)
-               import_marks(import_filename);
+               import_marks(import_filename, 0);
+       else if (import_filename_if_exists)
+               import_marks(import_filename_if_exists, 1);
        lastimportid = last_idnum;
 
        if (import_filename && revs.prune_data.nr)
index 24d382b..863c858 100644 (file)
@@ -59,7 +59,8 @@ static int verbosity, deepen_relative, set_upstream;
 static int progress = -1;
 static int enable_auto_gc = 1;
 static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
-static int max_children = 1;
+static int max_jobs = -1, submodule_fetch_jobs_config = -1;
+static int fetch_parallel_config = 1;
 static enum transport_family family;
 static const char *depth;
 static const char *deepen_since;
@@ -101,13 +102,20 @@ static int git_fetch_config(const char *k, const char *v, void *cb)
        }
 
        if (!strcmp(k, "submodule.fetchjobs")) {
-               max_children = parse_submodule_fetchjobs(k, v);
+               submodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);
                return 0;
        } else if (!strcmp(k, "fetch.recursesubmodules")) {
                recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);
                return 0;
        }
 
+       if (!strcmp(k, "fetch.parallel")) {
+               fetch_parallel_config = git_config_int(k, v);
+               if (fetch_parallel_config < 0)
+                       die(_("fetch.parallel cannot be negative"));
+               return 0;
+       }
+
        return git_default_config(k, v, cb);
 }
 
@@ -141,7 +149,7 @@ static struct option builtin_fetch_options[] = {
                    N_("fetch all tags and associated objects"), TAGS_SET),
        OPT_SET_INT('n', NULL, &tags,
                    N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
-       OPT_INTEGER('j', "jobs", &max_children,
+       OPT_INTEGER('j', "jobs", &max_jobs,
                    N_("number of submodules fetched in parallel")),
        OPT_BOOL('p', "prune", &prune,
                 N_("prune remote-tracking branches no longer on remote")),
@@ -256,20 +264,21 @@ static void create_fetch_oidset(struct ref **head, struct oidset *out)
 }
 
 struct refname_hash_entry {
-       struct hashmap_entry ent; /* must be the first member */
+       struct hashmap_entry ent;
        struct object_id oid;
        int ignore;
        char refname[FLEX_ARRAY];
 };
 
 static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data,
-                                 const void *e1_,
-                                 const void *e2_,
+                                 const struct hashmap_entry *eptr,
+                                 const struct hashmap_entry *entry_or_key,
                                  const void *keydata)
 {
-       const struct refname_hash_entry *e1 = e1_;
-       const struct refname_hash_entry *e2 = e2_;
+       const struct refname_hash_entry *e1, *e2;
 
+       e1 = container_of(eptr, const struct refname_hash_entry, ent);
+       e2 = container_of(entry_or_key, const struct refname_hash_entry, ent);
        return strcmp(e1->refname, keydata ? keydata : e2->refname);
 }
 
@@ -281,9 +290,9 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map,
        size_t len = strlen(refname);
 
        FLEX_ALLOC_MEM(ent, refname, refname, len);
-       hashmap_entry_init(ent, strhash(refname));
+       hashmap_entry_init(&ent->ent, strhash(refname));
        oidcpy(&ent->oid, oid);
-       hashmap_add(map, ent);
+       hashmap_add(map, &ent->ent);
        return ent;
 }
 
@@ -372,7 +381,7 @@ static void find_non_local_tags(const struct ref *refs,
                item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid);
                string_list_insert(&remote_refs_list, ref->name);
        }
-       hashmap_free(&existing_refs, 1);
+       hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);
 
        /*
         * We may have a final lightweight tag that needs to be
@@ -390,8 +399,10 @@ static void find_non_local_tags(const struct ref *refs,
        for_each_string_list_item(remote_ref_item, &remote_refs_list) {
                const char *refname = remote_ref_item->string;
                struct ref *rm;
+               unsigned int hash = strhash(refname);
 
-               item = hashmap_get_from_hash(&remote_refs, strhash(refname), refname);
+               item = hashmap_get_entry_from_hash(&remote_refs, hash, refname,
+                                       struct refname_hash_entry, ent);
                if (!item)
                        BUG("unseen remote ref?");
 
@@ -405,7 +416,7 @@ static void find_non_local_tags(const struct ref *refs,
                **tail = rm;
                *tail = &rm->next;
        }
-       hashmap_free(&remote_refs, 1);
+       hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent);
        string_list_clear(&remote_refs_list, 0);
        oidset_clear(&fetch_oids);
 }
@@ -524,17 +535,18 @@ static struct ref *get_ref_map(struct remote *remote,
                if (rm->peer_ref) {
                        const char *refname = rm->peer_ref->name;
                        struct refname_hash_entry *peer_item;
+                       unsigned int hash = strhash(refname);
 
-                       peer_item = hashmap_get_from_hash(&existing_refs,
-                                                         strhash(refname),
-                                                         refname);
+                       peer_item = hashmap_get_entry_from_hash(&existing_refs,
+                                               hash, refname,
+                                               struct refname_hash_entry, ent);
                        if (peer_item) {
                                struct object_id *old_oid = &peer_item->oid;
                                oidcpy(&rm->peer_ref->old_oid, old_oid);
                        }
                }
        }
-       hashmap_free(&existing_refs, 1);
+       hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent);
 
        return ref_map;
 }
@@ -1073,8 +1085,11 @@ static int check_exist_and_connected(struct ref *ref_map)
 static int fetch_refs(struct transport *transport, struct ref *ref_map)
 {
        int ret = check_exist_and_connected(ref_map);
-       if (ret)
+       if (ret) {
+               trace2_region_enter("fetch", "fetch_refs", the_repository);
                ret = transport_fetch_refs(transport, ref_map);
+               trace2_region_leave("fetch", "fetch_refs", the_repository);
+       }
        if (!ret)
                /*
                 * Keep the new pack's ".keep" file around to allow the caller
@@ -1090,11 +1105,14 @@ static int consume_refs(struct transport *transport, struct ref *ref_map)
 {
        int connectivity_checked = transport->smart_options
                ? transport->smart_options->connectivity_checked : 0;
-       int ret = store_updated_refs(transport->url,
-                                    transport->remote->name,
-                                    connectivity_checked,
-                                    ref_map);
+       int ret;
+       trace2_region_enter("fetch", "consume_refs", the_repository);
+       ret = store_updated_refs(transport->url,
+                                transport->remote->name,
+                                connectivity_checked,
+                                ref_map);
        transport_unlock_pack(transport);
+       trace2_region_leave("fetch", "consume_refs", the_repository);
        return ret;
 }
 
@@ -1339,9 +1357,11 @@ static int do_fetch(struct transport *transport,
                        argv_array_push(&ref_prefixes, "refs/tags/");
        }
 
-       if (must_list_refs)
+       if (must_list_refs) {
+               trace2_region_enter("fetch", "remote_refs", the_repository);
                remote_refs = transport_get_remote_refs(transport, &ref_prefixes);
-       else
+               trace2_region_leave("fetch", "remote_refs", the_repository);
+       } else
                remote_refs = NULL;
 
        argv_array_clear(&ref_prefixes);
@@ -1513,7 +1533,62 @@ static void add_options_to_argv(struct argv_array *argv)
 
 }
 
-static int fetch_multiple(struct string_list *list)
+/* Fetch multiple remotes in parallel */
+
+struct parallel_fetch_state {
+       const char **argv;
+       struct string_list *remotes;
+       int next, result;
+};
+
+static int fetch_next_remote(struct child_process *cp, struct strbuf *out,
+                            void *cb, void **task_cb)
+{
+       struct parallel_fetch_state *state = cb;
+       char *remote;
+
+       if (state->next < 0 || state->next >= state->remotes->nr)
+               return 0;
+
+       remote = state->remotes->items[state->next++].string;
+       *task_cb = remote;
+
+       argv_array_pushv(&cp->args, state->argv);
+       argv_array_push(&cp->args, remote);
+       cp->git_cmd = 1;
+
+       if (verbosity >= 0)
+               printf(_("Fetching %s\n"), remote);
+
+       return 1;
+}
+
+static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb)
+{
+       struct parallel_fetch_state *state = cb;
+       const char *remote = task_cb;
+
+       state->result = error(_("Could not fetch %s"), remote);
+
+       return 0;
+}
+
+static int fetch_finished(int result, struct strbuf *out,
+                         void *cb, void *task_cb)
+{
+       struct parallel_fetch_state *state = cb;
+       const char *remote = task_cb;
+
+       if (result) {
+               strbuf_addf(out, _("could not fetch '%s' (exit code: %d)\n"),
+                           remote, result);
+               state->result = -1;
+       }
+
+       return 0;
+}
+
+static int fetch_multiple(struct string_list *list, int max_children)
 {
        int i, result = 0;
        struct argv_array argv = ARGV_ARRAY_INIT;
@@ -1527,20 +1602,34 @@ static int fetch_multiple(struct string_list *list)
        argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", NULL);
        add_options_to_argv(&argv);
 
-       for (i = 0; i < list->nr; i++) {
-               const char *name = list->items[i].string;
-               argv_array_push(&argv, name);
-               if (verbosity >= 0)
-                       printf(_("Fetching %s\n"), name);
-               if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
-                       error(_("Could not fetch %s"), name);
-                       result = 1;
+       if (max_children != 1 && list->nr != 1) {
+               struct parallel_fetch_state state = { argv.argv, list, 0, 0 };
+
+               argv_array_push(&argv, "--end-of-options");
+               result = run_processes_parallel_tr2(max_children,
+                                                   &fetch_next_remote,
+                                                   &fetch_failed_to_start,
+                                                   &fetch_finished,
+                                                   &state,
+                                                   "fetch", "parallel/fetch");
+
+               if (!result)
+                       result = state.result;
+       } else
+               for (i = 0; i < list->nr; i++) {
+                       const char *name = list->items[i].string;
+                       argv_array_push(&argv, name);
+                       if (verbosity >= 0)
+                               printf(_("Fetching %s\n"), name);
+                       if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) {
+                               error(_("Could not fetch %s"), name);
+                               result = 1;
+                       }
+                       argv_array_pop(&argv);
                }
-               argv_array_pop(&argv);
-       }
 
        argv_array_clear(&argv);
-       return result;
+       return !!result;
 }
 
 /*
@@ -1666,14 +1755,13 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
 
        packet_trace_identity("fetch");
 
-       fetch_if_missing = 0;
-
        /* Record the command line for the reflog */
        strbuf_addstr(&default_rla, "fetch");
        for (i = 1; i < argc; i++)
                strbuf_addf(&default_rla, " %s", argv[i]);
 
-       fetch_config_from_gitmodules(&max_children, &recurse_submodules);
+       fetch_config_from_gitmodules(&submodule_fetch_jobs_config,
+                                    &recurse_submodules);
        git_config(git_fetch_config, NULL);
 
        argc = parse_options(argc, argv, prefix,
@@ -1734,20 +1822,34 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                }
        }
 
+       fetch_if_missing = 0;
+
        if (remote) {
                if (filter_options.choice || has_promisor_remote())
                        fetch_one_setup_partial(remote);
                result = fetch_one(remote, argc, argv, prune_tags_ok);
        } else {
+               int max_children = max_jobs;
+
                if (filter_options.choice)
                        die(_("--filter can only be used with the remote "
                              "configured in extensions.partialclone"));
+
+               if (max_children < 0)
+                       max_children = fetch_parallel_config;
+
                /* TODO should this also die if we have a previous partial-clone? */
-               result = fetch_multiple(&list);
+               result = fetch_multiple(&list, max_children);
        }
 
        if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) {
                struct argv_array options = ARGV_ARRAY_INIT;
+               int max_children = max_jobs;
+
+               if (max_children < 0)
+                       max_children = submodule_fetch_jobs_config;
+               if (max_children < 0)
+                       max_children = fetch_parallel_config;
 
                add_options_to_argv(&options);
                result = fetch_populated_submodules(the_repository,
index 69ac053..50ce8d9 100644 (file)
@@ -1147,5 +1147,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                run_pager(&opt, prefix);
        clear_pathspec(&pathspec);
        free_grep_patterns(&opt);
+       grep_destroy();
        return !hit;
 }
index c4b35fd..a26f223 100644 (file)
@@ -37,6 +37,7 @@
 #include "range-diff.h"
 
 #define MAIL_DEFAULT_WRAP 72
+#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -765,23 +766,51 @@ static void add_header(const char *value)
        item->string[len] = '\0';
 }
 
-#define THREAD_SHALLOW 1
-#define THREAD_DEEP 2
-static int thread;
+enum cover_setting {
+       COVER_UNSET,
+       COVER_OFF,
+       COVER_ON,
+       COVER_AUTO
+};
+
+enum thread_level {
+       THREAD_UNSET,
+       THREAD_SHALLOW,
+       THREAD_DEEP
+};
+
+enum cover_from_description {
+       COVER_FROM_NONE,
+       COVER_FROM_MESSAGE,
+       COVER_FROM_SUBJECT,
+       COVER_FROM_AUTO
+};
+
+static enum thread_level thread;
 static int do_signoff;
 static int base_auto;
 static char *from;
 static const char *signature = git_version_string;
 static const char *signature_file;
-static int config_cover_letter;
+static enum cover_setting config_cover_letter;
 static const char *config_output_directory;
+static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE;
 
-enum {
-       COVER_UNSET,
-       COVER_OFF,
-       COVER_ON,
-       COVER_AUTO
-};
+static enum cover_from_description parse_cover_from_description(const char *arg)
+{
+       if (!arg || !strcmp(arg, "default"))
+               return COVER_FROM_MESSAGE;
+       else if (!strcmp(arg, "none"))
+               return COVER_FROM_NONE;
+       else if (!strcmp(arg, "message"))
+               return COVER_FROM_MESSAGE;
+       else if (!strcmp(arg, "subject"))
+               return COVER_FROM_SUBJECT;
+       else if (!strcmp(arg, "auto"))
+               return COVER_FROM_AUTO;
+       else
+               die(_("%s: invalid cover from description mode"), arg);
+}
 
 static int git_format_config(const char *var, const char *value, void *cb)
 {
@@ -836,7 +865,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
                        thread = THREAD_SHALLOW;
                        return 0;
                }
-               thread = git_config_bool(var, value) && THREAD_SHALLOW;
+               thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET;
                return 0;
        }
        if (!strcmp(var, "format.signoff")) {
@@ -888,6 +917,10 @@ static int git_format_config(const char *var, const char *value, void *cb)
                }
                return 0;
        }
+       if (!strcmp(var, "format.coverfromdescription")) {
+               cover_from_description_mode = parse_cover_from_description(value);
+               return 0;
+       }
 
        return git_log_config(var, value, cb);
 }
@@ -994,20 +1027,6 @@ static void print_signature(FILE *file)
        putc('\n', file);
 }
 
-static void add_branch_description(struct strbuf *buf, const char *branch_name)
-{
-       struct strbuf desc = STRBUF_INIT;
-       if (!branch_name || !*branch_name)
-               return;
-       read_branch_desc(&desc, branch_name);
-       if (desc.len) {
-               strbuf_addch(buf, '\n');
-               strbuf_addbuf(buf, &desc);
-               strbuf_addch(buf, '\n');
-       }
-       strbuf_release(&desc);
-}
-
 static char *find_branch_name(struct rev_info *rev)
 {
        int i, positive = -1;
@@ -1054,6 +1073,44 @@ static void show_diffstat(struct rev_info *rev,
        fprintf(rev->diffopt.file, "\n");
 }
 
+static void prepare_cover_text(struct pretty_print_context *pp,
+                              const char *branch_name,
+                              struct strbuf *sb,
+                              const char *encoding,
+                              int need_8bit_cte)
+{
+       const char *subject = "*** SUBJECT HERE ***";
+       const char *body = "*** BLURB HERE ***";
+       struct strbuf description_sb = STRBUF_INIT;
+       struct strbuf subject_sb = STRBUF_INIT;
+
+       if (cover_from_description_mode == COVER_FROM_NONE)
+               goto do_pp;
+
+       if (branch_name && *branch_name)
+               read_branch_desc(&description_sb, branch_name);
+       if (!description_sb.len)
+               goto do_pp;
+
+       if (cover_from_description_mode == COVER_FROM_SUBJECT ||
+                       cover_from_description_mode == COVER_FROM_AUTO)
+               body = format_subject(&subject_sb, description_sb.buf, " ");
+
+       if (cover_from_description_mode == COVER_FROM_MESSAGE ||
+                       (cover_from_description_mode == COVER_FROM_AUTO &&
+                        subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN))
+               body = description_sb.buf;
+       else
+               subject = subject_sb.buf;
+
+do_pp:
+       pp_title_line(pp, &subject, sb, encoding, need_8bit_cte);
+       pp_remainder(pp, &body, sb, 0);
+
+       strbuf_release(&description_sb);
+       strbuf_release(&subject_sb);
+}
+
 static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              struct commit *origin,
                              int nr, struct commit **list,
@@ -1061,8 +1118,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              int quiet)
 {
        const char *committer;
-       const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
-       const char *msg;
        struct shortlog log;
        struct strbuf sb = STRBUF_INIT;
        int i;
@@ -1092,15 +1147,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        if (!branch_name)
                branch_name = find_branch_name(rev);
 
-       msg = body;
        pp.fmt = CMIT_FMT_EMAIL;
        pp.date_mode.type = DATE_RFC2822;
        pp.rev = rev;
        pp.print_email_subject = 1;
        pp_user_info(&pp, NULL, &sb, committer, encoding);
-       pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
-       pp_remainder(&pp, &msg, &sb, 0);
-       add_branch_description(&sb, branch_name);
+       prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte);
        fprintf(rev->diffopt.file, "%s\n", sb.buf);
 
        strbuf_release(&sb);
@@ -1249,9 +1301,9 @@ static int output_directory_callback(const struct option *opt, const char *arg,
 
 static int thread_callback(const struct option *opt, const char *arg, int unset)
 {
-       int *thread = (int *)opt->value;
+       enum thread_level *thread = (enum thread_level *)opt->value;
        if (unset)
-               *thread = 0;
+               *thread = THREAD_UNSET;
        else if (!arg || !strcmp(arg, "shallow"))
                *thread = THREAD_SHALLOW;
        else if (!strcmp(arg, "deep"))
@@ -1542,6 +1594,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int use_patch_format = 0;
        int quiet = 0;
        int reroll_count = -1;
+       char *cover_from_description_arg = NULL;
        char *branch_name = NULL;
        char *base_commit = NULL;
        struct base_tree_info bases;
@@ -1578,6 +1631,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                { OPTION_CALLBACK, 0, "rfc", &rev, NULL,
                            N_("Use [RFC PATCH] instead of [PATCH]"),
                            PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback },
+               OPT_STRING(0, "cover-from-description", &cover_from_description_arg,
+                           N_("cover-from-description-mode"),
+                           N_("generate parts of a cover letter based on a branch's description")),
                { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
                            N_("Use [<prefix>] instead of [PATCH]"),
                            PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1669,6 +1725,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                             PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
                             PARSE_OPT_KEEP_DASHDASH);
 
+       if (cover_from_description_arg)
+               cover_from_description_mode = parse_cover_from_description(cover_from_description_arg);
+
        if (0 < reroll_count) {
                struct strbuf sprefix = STRBUF_INIT;
                strbuf_addf(&sprefix, "%s v%d",
@@ -1766,10 +1825,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                setup_pager();
 
        if (output_directory) {
+               int saved;
                if (rev.diffopt.use_color != GIT_COLOR_ALWAYS)
                        rev.diffopt.use_color = GIT_COLOR_NEVER;
                if (use_stdout)
                        die(_("standard output, or directory, which one?"));
+               /*
+                * We consider <outdir> as 'outside of gitdir', therefore avoid
+                * applying adjust_shared_perm in s-c-l-d.
+                */
+               saved = get_shared_repository();
+               set_shared_repository(0);
+               switch (safe_create_leading_directories_const(output_directory)) {
+               case SCLD_OK:
+               case SCLD_EXISTS:
+                       break;
+               default:
+                       die(_("could not create leading directories "
+                             "of '%s'"), output_directory);
+               }
+               set_shared_repository(saved);
                if (mkdir(output_directory, 0777) < 0 && errno != EEXIST)
                        die_errno(_("could not create directory '%s'"),
                                  output_directory);
index 5b910e3..a4bfd8f 100644 (file)
@@ -1,3 +1,4 @@
+#include "cache.h"
 #include "builtin.h"
 #include "commit.h"
 #include "tag.h"
@@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die(_("not handling anything other than two heads merge."));
 
+       if (repo_read_index_unmerged(the_repository))
+               die_resolve_conflict("merge");
+
        o.branch1 = argv[++i];
        o.branch2 = argv[++i];
 
index 02e97f5..95456f3 100644 (file)
@@ -513,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix)
                }
        }
 
-       if (argc < 2) {
+       if (argc < 1) {
                error(_("too few parameters"));
                usage_with_options(git_notes_copy_usage, options);
        }
index 3742daf..843f5b2 100644 (file)
@@ -143,8 +143,8 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p)
        return remote->url_nr;
 }
 
-static NORETURN int die_push_simple(struct branch *branch,
-                                   struct remote *remote)
+static NORETURN void die_push_simple(struct branch *branch,
+                                    struct remote *remote)
 {
        /*
         * There's no point in using shorten_unambiguous_ref here,
@@ -357,8 +357,10 @@ static int push_with_options(struct transport *transport, struct refspec *rs,
 
        if (verbosity > 0)
                fprintf(stderr, _("Pushing to %s\n"), transport->url);
+       trace2_region_enter("push", "transport_push", the_repository);
        err = transport_push(the_repository, transport,
                             rs, flags, &reject_reasons);
+       trace2_region_leave("push", "transport_push", the_repository);
        if (err != 0) {
                fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR));
                error(_("failed to push some refs to '%s'"), transport->url);
index 4a20582..e755087 100644 (file)
@@ -1471,9 +1471,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
                        N_("let the user edit the list of commits to rebase"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG,
                        parse_opt_interactive },
-               OPT_SET_INT('p', "preserve-merges", &options.type,
-                           N_("(DEPRECATED) try to recreate merges instead of "
-                              "ignoring them"), REBASE_PRESERVE_MERGES),
+               OPT_SET_INT_F('p', "preserve-merges", &options.type,
+                             N_("(DEPRECATED) try to recreate merges instead of "
+                                "ignoring them"),
+                             REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
                OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
                OPT_BOOL('k', "keep-empty", &options.keep_empty,
                         N_("preserve empty commits during rebase")),
index 094c2f8..78b23d7 100644 (file)
@@ -233,6 +233,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args,
                /*
                 * pack-objects creates the .pack and .idx files, but not the
                 * .promisor file. Create the .promisor file, which is empty.
+                *
+                * NEEDSWORK: fetch-pack sometimes generates non-empty
+                * .promisor files containing the ref names and associated
+                * hashes at the point of generation of the corresponding
+                * packfile, but this would not preserve their contents. Maybe
+                * concatenate the contents of all .promisor files instead of
+                * just creating a new empty file.
                 */
                promisor_name = mkpathdup("%s-%s.promisor", packtmp,
                                          line.buf);
index 4fc44b3..4e80617 100644 (file)
@@ -427,6 +427,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info,
                                return error(_("could not save index tree"));
 
                        reset_head();
+                       discard_cache();
+                       read_cache();
                }
        }
 
@@ -1390,7 +1392,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
                        struct child_process cp = CHILD_PROCESS_INIT;
                        cp.git_cmd = 1;
                        argv_array_pushl(&cp.args, "reset", "--hard", "-q",
-                                        NULL);
+                                        "--no-recurse-submodules", NULL);
                        if (run_command(&cp)) {
                                ret = -1;
                                goto done;
index 62edee4..1bd1b23 100644 (file)
@@ -609,11 +609,66 @@ static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *pat
        return it;
 }
 
+static int write_index_as_tree_internal(struct object_id *oid,
+                                       struct index_state *index_state,
+                                       int cache_tree_valid,
+                                       int flags,
+                                       const char *prefix)
+{
+       if (flags & WRITE_TREE_IGNORE_CACHE_TREE) {
+               cache_tree_free(&index_state->cache_tree);
+               cache_tree_valid = 0;
+       }
+
+       if (!index_state->cache_tree)
+               index_state->cache_tree = cache_tree();
+
+       if (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)
+               return WRITE_TREE_UNMERGED_INDEX;
+
+       if (prefix) {
+               struct cache_tree *subtree;
+               subtree = cache_tree_find(index_state->cache_tree, prefix);
+               if (!subtree)
+                       return WRITE_TREE_PREFIX_ERROR;
+               oidcpy(oid, &subtree->oid);
+       }
+       else
+               oidcpy(oid, &index_state->cache_tree->oid);
+
+       return 0;
+}
+
+struct tree* write_in_core_index_as_tree(struct repository *repo) {
+       struct object_id o;
+       int was_valid, ret;
+
+       struct index_state *index_state = repo->index;
+       was_valid = index_state->cache_tree &&
+                   cache_tree_fully_valid(index_state->cache_tree);
+
+       ret = write_index_as_tree_internal(&o, index_state, was_valid, 0, NULL);
+       if (ret == WRITE_TREE_UNMERGED_INDEX) {
+               int i;
+               fprintf(stderr, "BUG: There are unmerged index entries:\n");
+               for (i = 0; i < index_state->cache_nr; i++) {
+                       const struct cache_entry *ce = index_state->cache[i];
+                       if (ce_stage(ce))
+                               fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
+                                       (int)ce_namelen(ce), ce->name);
+               }
+               BUG("unmerged index entries when writing inmemory index");
+       }
+
+       return lookup_tree(repo, &index_state->cache_tree->oid);
+}
+
+
 int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix)
 {
        int entries, was_valid;
        struct lock_file lock_file = LOCK_INIT;
-       int ret = 0;
+       int ret;
 
        hold_lock_file_for_update(&lock_file, index_path, LOCK_DIE_ON_ERROR);
 
@@ -622,18 +677,14 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,
                ret = WRITE_TREE_UNREADABLE_INDEX;
                goto out;
        }
-       if (flags & WRITE_TREE_IGNORE_CACHE_TREE)
-               cache_tree_free(&index_state->cache_tree);
 
-       if (!index_state->cache_tree)
-               index_state->cache_tree = cache_tree();
+       was_valid = !(flags & WRITE_TREE_IGNORE_CACHE_TREE) &&
+                   index_state->cache_tree &&
+                   cache_tree_fully_valid(index_state->cache_tree);
 
-       was_valid = cache_tree_fully_valid(index_state->cache_tree);
-       if (!was_valid) {
-               if (cache_tree_update(index_state, flags) < 0) {
-                       ret = WRITE_TREE_UNMERGED_INDEX;
-                       goto out;
-               }
+       ret = write_index_as_tree_internal(oid, index_state, was_valid, flags,
+                                          prefix);
+       if (!ret && !was_valid) {
                write_locked_index(index_state, &lock_file, COMMIT_LOCK);
                /* Not being able to write is fine -- we are only interested
                 * in updating the cache-tree part, and if the next caller
@@ -643,18 +694,6 @@ int write_index_as_tree(struct object_id *oid, struct index_state *index_state,
                 */
        }
 
-       if (prefix) {
-               struct cache_tree *subtree;
-               subtree = cache_tree_find(index_state->cache_tree, prefix);
-               if (!subtree) {
-                       ret = WRITE_TREE_PREFIX_ERROR;
-                       goto out;
-               }
-               oidcpy(oid, &subtree->oid);
-       }
-       else
-               oidcpy(oid, &index_state->cache_tree->oid);
-
 out:
        rollback_lock_file(&lock_file);
        return ret;
index 757bbc4..639bfa5 100644 (file)
@@ -34,7 +34,7 @@ int cache_tree_fully_valid(struct cache_tree *);
 int cache_tree_update(struct index_state *, int);
 void cache_tree_verify(struct repository *, struct index_state *);
 
-/* bitmasks to write_cache_as_tree flags */
+/* bitmasks to write_index_as_tree flags */
 #define WRITE_TREE_MISSING_OK 1
 #define WRITE_TREE_IGNORE_CACHE_TREE 2
 #define WRITE_TREE_DRY_RUN 4
@@ -46,6 +46,7 @@ void cache_tree_verify(struct repository *, struct index_state *);
 #define WRITE_TREE_UNMERGED_INDEX (-2)
 #define WRITE_TREE_PREFIX_ERROR (-3)
 
+struct tree* write_in_core_index_as_tree(struct repository *repo);
 int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix);
 void prime_cache_tree(struct repository *, struct index_state *, struct tree *);
 
diff --git a/cache.h b/cache.h
index 76f38f7..04cabaa 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -748,6 +748,19 @@ struct cache_entry *index_file_exists(struct index_state *istate, const char *na
  */
 int index_name_pos(const struct index_state *, const char *name, int namelen);
 
+/*
+ * Some functions return the negative complement of an insert position when a
+ * precise match was not found but a position was found where the entry would
+ * need to be inserted. This helper protects that logic from any integer
+ * underflow.
+ */
+static inline int index_pos_to_insert_pos(uintmax_t pos)
+{
+       if (pos > INT_MAX)
+               die("overflow: -1 - %"PRIuMAX, pos);
+       return -1 - (int)pos;
+}
+
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
index 85a9d6b..4e64a19 100755 (executable)
@@ -40,9 +40,15 @@ osx-clang|osx-gcc)
        test -z "$BREW_INSTALL_PACKAGES" ||
        brew install $BREW_INSTALL_PACKAGES
        brew link --force gettext
+       brew cask install perforce || {
+               # Update the definitions and try again
+               git -C "$(brew --repository)"/Library/Taps/homebrew/homebrew-cask pull &&
+               brew cask install perforce
+       } ||
        brew install caskroom/cask/perforce
        case "$jobname" in
        osx-gcc)
+               brew link gcc ||
                brew link gcc@8
                ;;
        esac
index 7a17c14..4a38eed 100644 (file)
--- a/column.c
+++ b/column.c
@@ -23,18 +23,7 @@ struct column_data {
 /* return length of 's' in letters, ANSI escapes stripped */
 static int item_length(const char *s)
 {
-       int len, i = 0;
-       struct strbuf str = STRBUF_INIT;
-
-       strbuf_addstr(&str, s);
-       while ((s = strstr(str.buf + i, "\033[")) != NULL) {
-               int len = strspn(s + 2, "0123456789;");
-               i = s - str.buf;
-               strbuf_remove(&str, i, len + 3); /* \033[<len><func char> */
-       }
-       len = utf8_strwidth(str.buf);
-       strbuf_release(&str);
-       return len;
+       return utf8_strnwidth(s, -1, 1);
 }
 
 /*
index fc4a43b..0aea7b2 100644 (file)
@@ -41,6 +41,9 @@
 #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
                        + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz)
 
+/* Remember to update object flag allocation in object.h */
+#define REACHABLE       (1u<<15)
+
 char *get_commit_graph_filename(const char *obj_dir)
 {
        char *filename = xstrfmt("%s/info/commit-graph", obj_dir);
@@ -1030,11 +1033,11 @@ static void add_missing_parents(struct write_commit_graph_context *ctx, struct c
 {
        struct commit_list *parent;
        for (parent = commit->parents; parent; parent = parent->next) {
-               if (!(parent->item->object.flags & UNINTERESTING)) {
+               if (!(parent->item->object.flags & REACHABLE)) {
                        ALLOC_GROW(ctx->oids.list, ctx->oids.nr + 1, ctx->oids.alloc);
                        oidcpy(&ctx->oids.list[ctx->oids.nr], &(parent->item->object.oid));
                        ctx->oids.nr++;
-                       parent->item->object.flags |= UNINTERESTING;
+                       parent->item->object.flags |= REACHABLE;
                }
        }
 }
@@ -1052,7 +1055,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
                display_progress(ctx->progress, i + 1);
                commit = lookup_commit(ctx->r, &ctx->oids.list[i]);
                if (commit)
-                       commit->object.flags |= UNINTERESTING;
+                       commit->object.flags |= REACHABLE;
        }
        stop_progress(&ctx->progress);
 
@@ -1089,7 +1092,7 @@ static void close_reachable(struct write_commit_graph_context *ctx)
                commit = lookup_commit(ctx->r, &ctx->oids.list[i]);
 
                if (commit)
-                       commit->object.flags &= ~UNINTERESTING;
+                       commit->object.flags &= ~REACHABLE;
        }
        stop_progress(&ctx->progress);
 }
index 3ea1747..4ca7e70 100644 (file)
@@ -10,7 +10,6 @@
 #include "commit-reach.h"
 
 /* Remember to update object flag allocation in object.h */
-#define REACHABLE       (1u<<15)
 #define PARENT1                (1u<<16)
 #define PARENT2                (1u<<17)
 #define STALE          (1u<<18)
index 40890ae..52036bc 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -19,6 +19,7 @@
 #include "advice.h"
 #include "refs.h"
 #include "commit-reach.h"
+#include "run-command.h"
 
 static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
 
@@ -1581,3 +1582,26 @@ size_t ignore_non_trailer(const char *buf, size_t len)
        }
        return boc ? len - boc : len - cutoff;
 }
+
+int run_commit_hook(int editor_is_used, const char *index_file,
+                   const char *name, ...)
+{
+       struct argv_array hook_env = ARGV_ARRAY_INIT;
+       va_list args;
+       int ret;
+
+       argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file);
+
+       /*
+        * Let the hook know that no editor will be launched.
+        */
+       if (!editor_is_used)
+               argv_array_push(&hook_env, "GIT_EDITOR=:");
+
+       va_start(args, name);
+       ret = run_hook_ve(hook_env.argv,name, args);
+       va_end(args);
+       argv_array_clear(&hook_env);
+
+       return ret;
+}
index 06566c8..fe60923 100644 (file)
@@ -363,6 +363,8 @@ static inline int needs_hiding(const char *path)
                        /* ignore trailing slashes */
                        if (*path)
                                basename = path;
+                       else
+                               break;
                }
 
        if (hide_dotfiles == HIDE_DOTFILES_TRUE)
@@ -1665,6 +1667,8 @@ char *mingw_getenv(const char *name)
        if (!w_key)
                die("Out of memory, (tried to allocate %u wchar_t's)", len_key);
        xutftowcs(w_key, name, len_key);
+       /* GetEnvironmentVariableW() only sets the last error upon failure */
+       SetLastError(ERROR_SUCCESS);
        len_value = GetEnvironmentVariableW(w_key, w_value, ARRAY_SIZE(w_value));
        if (!len_value && GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
                free(w_key);
index c7b021b..ec95a3b 100755 (executable)
@@ -68,8 +68,54 @@ while (@ARGV) {
        } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
                $arg =~ s/^-L/-LIBPATH:/;
                push(@lflags, $arg);
-       } elsif ("$arg" =~ /^-R/) {
+       } elsif ("$arg" =~ /^-[Rl]/) {
                # eat
+       } elsif ("$arg" eq "-Werror") {
+               push(@cflags, "-WX");
+       } elsif ("$arg" eq "-Wall") {
+               # cl.exe understands -Wall, but it is really overzealous
+               push(@cflags, "-W4");
+               # disable the "signed/unsigned mismatch" warnings; our source code violates that
+               push(@cflags, "-wd4018");
+               push(@cflags, "-wd4245");
+               push(@cflags, "-wd4389");
+               # disable the "unreferenced formal parameter" warning; our source code violates that
+               push(@cflags, "-wd4100");
+               # disable the "conditional expression is constant" warning; our source code violates that
+               push(@cflags, "-wd4127");
+               # disable the "const object should be initialized" warning; these warnings affect only objects that are `static`
+               push(@cflags, "-wd4132");
+               # disable the "function/data pointer conversion in expression" warning; our source code violates that
+               push(@cflags, "-wd4152");
+               # disable the "non-constant aggregate initializer" warning; our source code violates that
+               push(@cflags, "-wd4204");
+               # disable the "cannot be initialized using address of automatic variable" warning; our source code violates that
+               push(@cflags, "-wd4221");
+               # disable the "possible loss of data" warnings; our source code violates that
+               push(@cflags, "-wd4244");
+               push(@cflags, "-wd4267");
+               # disable the "array is too small to include a terminating null character" warning; we ab-use strings to initialize OIDs
+               push(@cflags, "-wd4295");
+               # disable the "'<<': result of 32-bit shift implicitly converted to 64 bits" warning; our source code violates that
+               push(@cflags, "-wd4334");
+               # disable the "declaration hides previous local declaration" warning; our source code violates that
+               push(@cflags, "-wd4456");
+               # disable the "declaration hides function parameter" warning; our source code violates that
+               push(@cflags, "-wd4457");
+               # disable the "declaration hides global declaration" warning; our source code violates that
+               push(@cflags, "-wd4459");
+               # disable the "potentially uninitialized local variable '<name>' used" warning; our source code violates that
+               push(@cflags, "-wd4701");
+               # disable the "unreachable code" warning; our source code violates that
+               push(@cflags, "-wd4702");
+               # disable the "potentially uninitialized local pointer variable used" warning; our source code violates that
+               push(@cflags, "-wd4703");
+               # disable the "assignment within conditional expression" warning; our source code violates that
+               push(@cflags, "-wd4706");
+               # disable the "'inet_ntoa': Use inet_ntop() or InetNtop() instead" warning; our source code violates that
+               push(@cflags, "-wd4996");
+       } elsif ("$arg" =~ /^-W[a-z]/) {
+               # let's ignore those
        } else {
                push(@args, $arg);
        }
index 0f70d43..8ed062a 100644 (file)
@@ -1,3 +1,6 @@
+#ifndef WIN32_PATH_UTILS_H
+#define WIN32_PATH_UTILS_H
+
 #define has_dos_drive_prefix(path) \
        (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
 int win32_skip_dos_drive_prefix(char **path);
@@ -18,3 +21,5 @@ static inline char *win32_find_last_dir_sep(const char *path)
 #define find_last_dir_sep win32_find_last_dir_sep
 int win32_offset_1st_component(const char *path);
 #define offset_1st_component win32_offset_1st_component
+
+#endif
index f1cfe73..737983d 100644 (file)
@@ -51,7 +51,7 @@ typedef struct {
 } pthread_t;
 
 int pthread_create(pthread_t *thread, const void *unused,
-                         void *(*start_routine)(void*), void *arg);
+                  void *(*start_routine)(void*), void *arg);
 
 /*
  * To avoid the need of copying a struct, we use small macro wrapper to pass
index cacd82c..54fd701 100644 (file)
@@ -546,7 +546,7 @@ static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
 typedef struct _OBJECT_NAME_INFORMATION
 {
        UNICODE_STRING Name;
-       WCHAR NameBuffer[0];
+       WCHAR NameBuffer[FLEX_ARRAY];
 } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION;
 
 #define ObjectNameInformation 1
index eef4f4f..e7052b3 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1856,9 +1856,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,
        if (git_config_parse_key(key, &normalized_key, NULL))
                return NULL;
 
-       hashmap_entry_init(&k, strhash(normalized_key));
+       hashmap_entry_init(&k.ent, strhash(normalized_key));
        k.key = normalized_key;
-       found_entry = hashmap_get(&cs->config_hash, &k, NULL);
+       found_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);
        free(normalized_key);
        return found_entry;
 }
@@ -1877,10 +1877,10 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
         */
        if (!e) {
                e = xmalloc(sizeof(*e));
-               hashmap_entry_init(e, strhash(key));
+               hashmap_entry_init(&e->ent, strhash(key));
                e->key = xstrdup(key);
                string_list_init(&e->value_list, 1);
-               hashmap_add(&cs->config_hash, e);
+               hashmap_add(&cs->config_hash, &e->ent);
        }
        si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
 
@@ -1908,12 +1908,14 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha
 }
 
 static int config_set_element_cmp(const void *unused_cmp_data,
-                                 const void *entry,
-                                 const void *entry_or_key,
+                                 const struct hashmap_entry *eptr,
+                                 const struct hashmap_entry *entry_or_key,
                                  const void *unused_keydata)
 {
-       const struct config_set_element *e1 = entry;
-       const struct config_set_element *e2 = entry_or_key;
+       const struct config_set_element *e1, *e2;
+
+       e1 = container_of(eptr, const struct config_set_element, ent);
+       e2 = container_of(entry_or_key, const struct config_set_element, ent);
 
        return strcmp(e1->key, e2->key);
 }
@@ -1934,12 +1936,12 @@ void git_configset_clear(struct config_set *cs)
        if (!cs->hash_initialized)
                return;
 
-       hashmap_iter_init(&cs->config_hash, &iter);
-       while ((entry = hashmap_iter_next(&iter))) {
+       hashmap_for_each_entry(&cs->config_hash, &iter, entry,
+                               ent /* member name */) {
                free(entry->key);
                string_list_clear(&entry->value_list, 1);
        }
-       hashmap_free(&cs->config_hash, 1);
+       hashmap_free_entries(&cs->config_hash, struct config_set_element, ent);
        cs->hash_initialized = 0;
        free(cs->list.items);
        cs->list.nr = 0;
index db7f06b..cc8efd9 100644 (file)
@@ -703,20 +703,24 @@ vcxproj:
        perl contrib/buildsystems/generate -g Vcxproj
        git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj
 
-       # Generate the LinkOrCopyBuiltins.targets file
+       # Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file
        (echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
         echo '  <Target Name="CopyBuiltins_AfterBuild" AfterTargets="AfterBuild">' && \
         for name in $(BUILT_INS);\
         do \
           echo '    <Copy SourceFiles="$$(OutDir)\git.exe" DestinationFiles="$$(OutDir)\'"$$name"'" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />'; \
         done && \
+        echo '  </Target>' && \
+        echo '</Project>') >git/LinkOrCopyBuiltins.targets
+       (echo '<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">' && \
+        echo '  <Target Name="CopyBuiltins_AfterBuild" AfterTargets="AfterBuild">' && \
         for name in $(REMOTE_CURL_ALIASES); \
         do \
           echo '    <Copy SourceFiles="$$(OutDir)\'"$(REMOTE_CURL_PRIMARY)"'" DestinationFiles="$$(OutDir)\'"$$name"'" SkipUnchangedFiles="true" UseHardlinksIfPossible="true" />'; \
         done && \
         echo '  </Target>' && \
-        echo '</Project>') >git/LinkOrCopyBuiltins.targets
-       git add -f git/LinkOrCopyBuiltins.targets
+        echo '</Project>') >git-remote-http/LinkOrCopyRemoteHttp.targets
+       git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets
 
        # Add command-list.h
        $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 command-list.h
@@ -724,11 +728,10 @@ vcxproj:
 
        # Add scripts
        rm -f perl/perl.mak
-       $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 \
-               $(SCRIPT_LIB) $(SCRIPT_SH_GEN) $(SCRIPT_PERL_GEN)
+       $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(SCRIPT_LIB) $(SCRIPTS)
        # Strip out the sane tool path, needed only for building
        sed -i '/^git_broken_path_fix ".*/d' git-sh-setup
-       git add -f $(SCRIPT_LIB) $(SCRIPT_SH_GEN) $(SCRIPT_PERL_GEN)
+       git add -f $(SCRIPT_LIB) $(SCRIPTS)
 
        # Add Perl module
        $(MAKE) $(LIB_PERL_GEN)
@@ -758,6 +761,10 @@ vcxproj:
        $(MAKE) -C templates
        git add -f templates/boilerplates.made templates/blt/
 
+       # Add the translated messages
+       make MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(MOFILES)
+       git add -f $(MOFILES)
+
        # Add build options
        $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 GIT-BUILD-OPTIONS
        git add -f GIT-BUILD-OPTIONS
index 7b1e277..5c666f9 100644 (file)
@@ -278,6 +278,9 @@ EOM
     if ($target eq 'git') {
       print F "  <Import Project=\"LinkOrCopyBuiltins.targets\" />\n";
     }
+    if ($target eq 'git-remote-http') {
+      print F "  <Import Project=\"LinkOrCopyRemoteHttp.targets\" />\n";
+    }
     print F << "EOM";
 </Project>
 EOM
diff --git a/contrib/coccinelle/hashmap.cocci b/contrib/coccinelle/hashmap.cocci
new file mode 100644 (file)
index 0000000..d69e120
--- /dev/null
@@ -0,0 +1,16 @@
+@ hashmap_entry_init_usage @
+expression E;
+struct hashmap_entry HME;
+@@
+- HME.hash = E;
++ hashmap_entry_init(&HME, E);
+
+@@
+identifier f !~ "^hashmap_entry_init$";
+expression E;
+struct hashmap_entry *HMEP;
+@@
+  f(...) {<...
+- HMEP->hash = E;
++ hashmap_entry_init(HMEP, E);
+  ...>}
index 886bf95..eef4eff 100644 (file)
@@ -11,8 +11,9 @@
 #
 #  zstyle ':completion:*:*:git:*' script ~/.git-completion.zsh
 #
-# The recommended way to install this script is to copy to '~/.zsh/_git', and
-# then add the following to your ~/.zshrc file:
+# The recommended way to install this script is to make a copy of it in
+# ~/.zsh/ directory as ~/.zsh/git-completion.zsh and then add the following
+# to your ~/.zshrc file:
 #
 #  fpath=(~/.zsh $fpath)
 
index 7440aa1..e258992 100644 (file)
@@ -72,7 +72,7 @@ sub handle_line {
              (?:$COLOR?\|$COLOR?[ ])* # zero or more trailing "|"
                                 [ ]*  # trailing whitespace for merges
            /x) {
-               my $graph_prefix = $&;
+               my $graph_prefix = $&;
 
                # We must flush before setting graph indent, since the
                # new commit may be indented differently from what we
diff --git a/diff.c b/diff.c
index 8fb57b7..afe4400 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -933,16 +933,18 @@ static int cmp_in_block_with_wsd(const struct diff_options *o,
 }
 
 static int moved_entry_cmp(const void *hashmap_cmp_fn_data,
-                          const void *entry,
-                          const void *entry_or_key,
+                          const struct hashmap_entry *eptr,
+                          const struct hashmap_entry *entry_or_key,
                           const void *keydata)
 {
        const struct diff_options *diffopt = hashmap_cmp_fn_data;
-       const struct moved_entry *a = entry;
-       const struct moved_entry *b = entry_or_key;
+       const struct moved_entry *a, *b;
        unsigned flags = diffopt->color_moved_ws_handling
                         & XDF_WHITESPACE_FLAGS;
 
+       a = container_of(eptr, const struct moved_entry, ent);
+       b = container_of(entry_or_key, const struct moved_entry, ent);
+
        if (diffopt->color_moved_ws_handling &
            COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE)
                /*
@@ -964,8 +966,9 @@ static struct moved_entry *prepare_entry(struct diff_options *o,
        struct moved_entry *ret = xmalloc(sizeof(*ret));
        struct emitted_diff_symbol *l = &o->emitted_symbols->buf[line_no];
        unsigned flags = o->color_moved_ws_handling & XDF_WHITESPACE_FLAGS;
+       unsigned int hash = xdiff_hash_string(l->line, l->len, flags);
 
-       ret->ent.hash = xdiff_hash_string(l->line, l->len, flags);
+       hashmap_entry_init(&ret->ent, hash);
        ret->es = l;
        ret->next_line = NULL;
 
@@ -1002,7 +1005,7 @@ static void add_lines_to_move_detection(struct diff_options *o,
                if (prev_line && prev_line->es->s == o->emitted_symbols->buf[n].s)
                        prev_line->next_line = key;
 
-               hashmap_add(hm, key);
+               hashmap_add(hm, &key->ent);
                prev_line = key;
        }
 }
@@ -1018,7 +1021,7 @@ static void pmb_advance_or_null(struct diff_options *o,
                struct moved_entry *prev = pmb[i].match;
                struct moved_entry *cur = (prev && prev->next_line) ?
                                prev->next_line : NULL;
-               if (cur && !hm->cmpfn(o, cur, match, NULL)) {
+               if (cur && !hm->cmpfn(o, &cur->ent, &match->ent, NULL)) {
                        pmb[i].match = cur;
                } else {
                        pmb[i].match = NULL;
@@ -1035,7 +1038,7 @@ static void pmb_advance_or_null_multi_match(struct diff_options *o,
        int i;
        char *got_match = xcalloc(1, pmb_nr);
 
-       for (; match; match = hashmap_get_next(hm, match)) {
+       hashmap_for_each_entry_from(hm, match, ent) {
                for (i = 0; i < pmb_nr; i++) {
                        struct moved_entry *prev = pmb[i].match;
                        struct moved_entry *cur = (prev && prev->next_line) ?
@@ -1143,13 +1146,13 @@ static void mark_color_as_moved(struct diff_options *o,
                case DIFF_SYMBOL_PLUS:
                        hm = del_lines;
                        key = prepare_entry(o, n);
-                       match = hashmap_get(hm, key, NULL);
+                       match = hashmap_get_entry(hm, key, ent, NULL);
                        free(key);
                        break;
                case DIFF_SYMBOL_MINUS:
                        hm = add_lines;
                        key = prepare_entry(o, n);
-                       match = hashmap_get(hm, key, NULL);
+                       match = hashmap_get_entry(hm, key, ent, NULL);
                        free(key);
                        break;
                default:
@@ -1188,7 +1191,7 @@ static void mark_color_as_moved(struct diff_options *o,
                         * The current line is the start of a new block.
                         * Setup the set of potential blocks.
                         */
-                       for (; match; match = hashmap_get_next(hm, match)) {
+                       hashmap_for_each_entry_from(hm, match, ent) {
                                ALLOC_GROW(pmb, pmb_nr + 1, pmb_alloc);
                                if (o->color_moved_ws_handling &
                                    COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) {
@@ -6230,8 +6233,10 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
                        if (o->color_moved == COLOR_MOVED_ZEBRA_DIM)
                                dim_moved_lines(o);
 
-                       hashmap_free(&add_lines, 1);
-                       hashmap_free(&del_lines, 1);
+                       hashmap_free_entries(&add_lines, struct moved_entry,
+                                               ent);
+                       hashmap_free_entries(&del_lines, struct moved_entry,
+                                               ent);
                }
 
                for (i = 0; i < esm.nr; i++)
index 9042936..531d7ad 100644 (file)
@@ -274,18 +274,17 @@ static int find_identical_files(struct hashmap *srcs,
                                struct diff_options *options)
 {
        int renames = 0;
-
        struct diff_filespec *target = rename_dst[dst_index].two;
        struct file_similarity *p, *best = NULL;
        int i = 100, best_score = -1;
+       unsigned int hash = hash_filespec(options->repo, target);
 
        /*
         * Find the best source match for specified destination.
         */
-       p = hashmap_get_from_hash(srcs,
-                                 hash_filespec(options->repo, target),
-                                 NULL);
-       for (; p; p = hashmap_get_next(srcs, p)) {
+       p = hashmap_get_entry_from_hash(srcs, hash, NULL,
+                                       struct file_similarity, entry);
+       hashmap_for_each_entry_from(srcs, p, entry) {
                int score;
                struct diff_filespec *source = p->filespec;
 
@@ -329,8 +328,8 @@ static void insert_file_table(struct repository *r,
        entry->index = index;
        entry->filespec = filespec;
 
-       hashmap_entry_init(entry, hash_filespec(r, filespec));
-       hashmap_add(table, entry);
+       hashmap_entry_init(&entry->entry, hash_filespec(r, filespec));
+       hashmap_add(table, &entry->entry);
 }
 
 /*
@@ -359,7 +358,7 @@ static int find_exact_renames(struct diff_options *options)
                renames += find_identical_files(&file_table, i, options);
 
        /* Free the hash data structure and entries */
-       hashmap_free(&file_table, 1);
+       hashmap_free_entries(&file_table, struct file_similarity, entry);
 
        return renames;
 }
index 1f9160b..9503d08 100644 (file)
@@ -2489,18 +2489,14 @@ static void parse_from_existing(struct branch *b)
        }
 }
 
-static int parse_from(struct branch *b)
+static int parse_objectish(struct branch *b, const char *objectish)
 {
-       const char *from;
        struct branch *s;
        struct object_id oid;
 
-       if (!skip_prefix(command_buf.buf, "from ", &from))
-               return 0;
-
        oidcpy(&oid, &b->branch_tree.versions[1].oid);
 
-       s = lookup_branch(from);
+       s = lookup_branch(objectish);
        if (b == s)
                die("Can't create a branch from itself: %s", b->name);
        else if (s) {
@@ -2508,8 +2504,8 @@ static int parse_from(struct branch *b)
                oidcpy(&b->oid, &s->oid);
                oidcpy(&b->branch_tree.versions[0].oid, t);
                oidcpy(&b->branch_tree.versions[1].oid, t);
-       } else if (*from == ':') {
-               uintmax_t idnum = parse_mark_ref_eol(from);
+       } else if (*objectish == ':') {
+               uintmax_t idnum = parse_mark_ref_eol(objectish);
                struct object_entry *oe = find_mark(idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2523,13 +2519,13 @@ static int parse_from(struct branch *b)
                        } else
                                parse_from_existing(b);
                }
-       } else if (!get_oid(from, &b->oid)) {
+       } else if (!get_oid(objectish, &b->oid)) {
                parse_from_existing(b);
                if (is_null_oid(&b->oid))
                        b->delete = 1;
        }
        else
-               die("Invalid ref name or SHA1 expression: %s", from);
+               die("Invalid ref name or SHA1 expression: %s", objectish);
 
        if (b->branch_tree.tree && !oideq(&oid, &b->branch_tree.versions[1].oid)) {
                release_tree_content_recursive(b->branch_tree.tree);
@@ -2540,6 +2536,26 @@ static int parse_from(struct branch *b)
        return 1;
 }
 
+static int parse_from(struct branch *b)
+{
+       const char *from;
+
+       if (!skip_prefix(command_buf.buf, "from ", &from))
+               return 0;
+
+       return parse_objectish(b, from);
+}
+
+static int parse_objectish_with_prefix(struct branch *b, const char *prefix)
+{
+       const char *base;
+
+       if (!skip_prefix(command_buf.buf, prefix, &base))
+               return 0;
+
+       return parse_objectish(b, base);
+}
+
 static struct hash_list *parse_merge(unsigned int *count)
 {
        struct hash_list *list = NULL, **tail = &list, *n;
@@ -2714,6 +2730,7 @@ static void parse_new_tag(const char *arg)
                first_tag = t;
        last_tag = t;
        read_next_command();
+       parse_mark();
 
        /* from ... */
        if (!skip_prefix(command_buf.buf, "from ", &from))
@@ -2770,7 +2787,7 @@ static void parse_new_tag(const char *arg)
        strbuf_addbuf(&new_data, &msg);
        free(tagger);
 
-       if (store_object(OBJ_TAG, &new_data, NULL, &t->oid, 0))
+       if (store_object(OBJ_TAG, &new_data, NULL, &t->oid, next_mark))
                t->pack_id = MAX_PACK_ID;
        else
                t->pack_id = pack_id;
@@ -2779,6 +2796,7 @@ static void parse_new_tag(const char *arg)
 static void parse_reset_branch(const char *arg)
 {
        struct branch *b;
+       const char *tag_name;
 
        b = lookup_branch(arg);
        if (b) {
@@ -2794,6 +2812,32 @@ static void parse_reset_branch(const char *arg)
                b = new_branch(arg);
        read_next_command();
        parse_from(b);
+       if (b->delete && skip_prefix(b->name, "refs/tags/", &tag_name)) {
+               /*
+                * Elsewhere, we call dump_branches() before dump_tags(),
+                * and dump_branches() will handle ref deletions first, so
+                * in order to make sure the deletion actually takes effect,
+                * we need to remove the tag from our list of tags to update.
+                *
+                * NEEDSWORK: replace list of tags with hashmap for faster
+                * deletion?
+                */
+               struct tag *t, *prev = NULL;
+               for (t = first_tag; t; t = t->next_tag) {
+                       if (!strcmp(t->name, tag_name))
+                               break;
+                       prev = t;
+               }
+               if (t) {
+                       if (prev)
+                               prev->next_tag = t->next_tag;
+                       else
+                               first_tag = t->next_tag;
+                       if (!t->next_tag)
+                               last_tag = prev;
+                       /* There is no mem_pool_free(t) function to call. */
+               }
+       }
        if (command_buf.len > 0)
                unread_command_buf = 1;
 }
@@ -3060,6 +3104,28 @@ static void parse_progress(void)
        skip_optional_lf();
 }
 
+static void parse_alias(void)
+{
+       struct object_entry *e;
+       struct branch b;
+
+       skip_optional_lf();
+       read_next_command();
+
+       /* mark ... */
+       parse_mark();
+       if (!next_mark)
+               die(_("Expected 'mark' command, got %s"), command_buf.buf);
+
+       /* to ... */
+       memset(&b, 0, sizeof(b));
+       if (!parse_objectish_with_prefix(&b, "to "))
+               die(_("Expected 'to' command, got %s"), command_buf.buf);
+       e = find_object(&b.oid);
+       assert(e);
+       insert_mark(next_mark, e);
+}
+
 static char* make_fast_import_path(const char *path)
 {
        if (!relative_marks_paths || is_absolute_path(path))
@@ -3187,6 +3253,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_import_marks(arg, from_stream, 1);
        } else if (skip_prefix(feature, "export-marks=", &arg)) {
                option_export_marks(arg);
+       } else if (!strcmp(feature, "alias")) {
+               ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "get-mark")) {
                ; /* Don't die - this feature is supported */
        } else if (!strcmp(feature, "cat-blob")) {
@@ -3343,6 +3411,8 @@ int cmd_main(int argc, const char **argv)
                        parse_checkpoint();
                else if (!strcmp("done", command_buf.buf))
                        break;
+               else if (!strcmp("alias", command_buf.buf))
+                       parse_alias();
                else if (starts_with(command_buf.buf, "progress "))
                        parse_progress();
                else if (skip_prefix(command_buf.buf, "feature ", &v))
index 947da54..f80e2d1 100644 (file)
@@ -382,6 +382,7 @@ static int find_common(struct fetch_negotiator *negotiator,
                state_len = 0;
        }
 
+       trace2_region_enter("fetch-pack", "negotiation_v0_v1", the_repository);
        flushes = 0;
        retval = -1;
        if (args->no_dependents)
@@ -466,6 +467,7 @@ static int find_common(struct fetch_negotiator *negotiator,
                }
        }
 done:
+       trace2_region_leave("fetch-pack", "negotiation_v0_v1", the_repository);
        if (!got_ready || !no_done) {
                packet_buf_write(&req_buf, "done\n");
                send_request(args, fd[1], &req_buf);
@@ -754,8 +756,33 @@ static int sideband_demux(int in, int out, void *data)
        return ret;
 }
 
+static void write_promisor_file(const char *keep_name,
+                               struct ref **sought, int nr_sought)
+{
+       struct strbuf promisor_name = STRBUF_INIT;
+       int suffix_stripped;
+       FILE *output;
+       int i;
+
+       strbuf_addstr(&promisor_name, keep_name);
+       suffix_stripped = strbuf_strip_suffix(&promisor_name, ".keep");
+       if (!suffix_stripped)
+               BUG("name of pack lockfile should end with .keep (was '%s')",
+                   keep_name);
+       strbuf_addstr(&promisor_name, ".promisor");
+
+       output = xfopen(promisor_name.buf, "w");
+       for (i = 0; i < nr_sought; i++)
+               fprintf(output, "%s %s\n", oid_to_hex(&sought[i]->old_oid),
+                       sought[i]->name);
+       fclose(output);
+
+       strbuf_release(&promisor_name);
+}
+
 static int get_pack(struct fetch_pack_args *args,
-                   int xd[2], char **pack_lockfile)
+                   int xd[2], char **pack_lockfile,
+                   struct ref **sought, int nr_sought)
 {
        struct async demux;
        int do_keep = args->keep_pack;
@@ -817,7 +844,13 @@ static int get_pack(struct fetch_pack_args *args,
                }
                if (args->check_self_contained_and_connected)
                        argv_array_push(&cmd.args, "--check-self-contained-and-connected");
-               if (args->from_promisor)
+               /*
+                * If we're obtaining the filename of a lockfile, we'll use
+                * that filename to write a .promisor file with more
+                * information below. If not, we need index-pack to do it for
+                * us.
+                */
+               if (!(do_keep && pack_lockfile) && args->from_promisor)
                        argv_array_push(&cmd.args, "--promisor");
        }
        else {
@@ -871,6 +904,14 @@ static int get_pack(struct fetch_pack_args *args,
                die(_("%s failed"), cmd_name);
        if (use_sideband && finish_async(&demux))
                die(_("error in sideband demultiplexer"));
+
+       /*
+        * Now that index-pack has succeeded, write the promisor file using the
+        * obtained .keep filename if necessary
+        */
+       if (do_keep && pack_lockfile && args->from_promisor)
+               write_promisor_file(*pack_lockfile, sought, nr_sought);
+
        return 0;
 }
 
@@ -1006,7 +1047,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
                alternate_shallow_file = setup_temporary_shallow(si->shallow);
        else
                alternate_shallow_file = NULL;
-       if (get_pack(args, fd, pack_lockfile))
+       if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
                die(_("git fetch-pack: fetch failed."));
 
  all_done:
@@ -1378,7 +1419,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
        enum fetch_state state = FETCH_CHECK_LOCAL;
        struct oidset common = OIDSET_INIT;
        struct packet_reader reader;
-       int in_vain = 0;
+       int in_vain = 0, negotiation_started = 0;
        int haves_to_send = INITIAL_FLUSH;
        struct fetch_negotiator negotiator;
        fetch_negotiator_init(r, &negotiator);
@@ -1421,6 +1462,12 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        }
                        break;
                case FETCH_SEND_REQUEST:
+                       if (!negotiation_started) {
+                               negotiation_started = 1;
+                               trace2_region_enter("fetch-pack",
+                                                   "negotiation_v2",
+                                                   the_repository);
+                       }
                        if (send_fetch_request(&negotiator, fd[1], args, ref,
                                               &common,
                                               &haves_to_send, &in_vain,
@@ -1444,6 +1491,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
                        }
                        break;
                case FETCH_GET_PACK:
+                       trace2_region_leave("fetch-pack",
+                                           "negotiation_v2",
+                                           the_repository);
                        /* Check for shallow-info section */
                        if (process_section_header(&reader, "shallow-info", 1))
                                receive_shallow_info(args, &reader, shallows, si);
@@ -1453,7 +1503,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
 
                        /* get the pack */
                        process_section_header(&reader, "packfile", 0);
-                       if (get_pack(args, fd, pack_lockfile))
+                       if (get_pack(args, fd, pack_lockfile, sought, nr_sought))
                                die(_("git fetch-pack: fetch failed."));
 
                        state = FETCH_DONE;
index 231e83a..1f4aa1b 100644 (file)
@@ -14,8 +14,13 @@ struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR);
 static void fsmonitor_ewah_callback(size_t pos, void *is)
 {
        struct index_state *istate = (struct index_state *)is;
-       struct cache_entry *ce = istate->cache[pos];
+       struct cache_entry *ce;
 
+       if (pos >= istate->cache_nr)
+               BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)",
+                   (uintmax_t)pos, istate->cache_nr);
+
+       ce = istate->cache[pos];
        ce->ce_flags &= ~CE_FSMONITOR_VALID;
 }
 
@@ -50,17 +55,24 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data,
        }
        istate->fsmonitor_dirty = fsmonitor_dirty;
 
+       if (istate->fsmonitor_dirty->bit_size > istate->cache_nr)
+               BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",
+                   (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr);
+
        trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful");
        return 0;
 }
 
 void fill_fsmonitor_bitmap(struct index_state *istate)
 {
-       unsigned int i;
+       unsigned int i, skipped = 0;
        istate->fsmonitor_dirty = ewah_new();
-       for (i = 0; i < istate->cache_nr; i++)
-               if (!(istate->cache[i]->ce_flags & CE_FSMONITOR_VALID))
-                       ewah_set(istate->fsmonitor_dirty, i);
+       for (i = 0; i < istate->cache_nr; i++) {
+               if (istate->cache[i]->ce_flags & CE_REMOVE)
+                       skipped++;
+               else if (!(istate->cache[i]->ce_flags & CE_FSMONITOR_VALID))
+                       ewah_set(istate->fsmonitor_dirty, i - skipped);
+       }
 }
 
 void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
@@ -71,6 +83,10 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
        uint32_t ewah_size = 0;
        int fixup = 0;
 
+       if (istate->fsmonitor_dirty->bit_size > istate->cache_nr)
+               BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",
+                   (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr);
+
        put_be32(&hdr_version, INDEX_EXTENSION_VERSION);
        strbuf_add(sb, &hdr_version, sizeof(uint32_t));
 
@@ -236,6 +252,9 @@ void tweak_fsmonitor(struct index_state *istate)
                        }
 
                        /* Mark all previously saved entries as dirty */
+                       if (istate->fsmonitor_dirty->bit_size > istate->cache_nr)
+                               BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)",
+                                   (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr);
                        ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate);
 
                        /* Now mark the untracked cache for fsmonitor usage */
index a8b5854..607dca7 100644 (file)
@@ -77,7 +77,7 @@
 #endif
 /*
  * ARRAY_SIZE - get the number of elements in a visible array
- *  <at> x: the array whose size you want.
+ * @x: the array whose size you want.
  *
  * This does not work on pointers, or arrays declared as [], or
  * function parameters.  With correct compiler support, such usage
@@ -1313,4 +1313,42 @@ void unleak_memory(const void *ptr, size_t len);
  */
 #include "banned.h"
 
+/*
+ * container_of - Get the address of an object containing a field.
+ *
+ * @ptr: pointer to the field.
+ * @type: type of the object.
+ * @member: name of the field within the object.
+ */
+#define container_of(ptr, type, member) \
+       ((type *) ((char *)(ptr) - offsetof(type, member)))
+
+/*
+ * helper function for `container_of_or_null' to avoid multiple
+ * evaluation of @ptr
+ */
+static inline void *container_of_or_null_offset(void *ptr, size_t offset)
+{
+       return ptr ? (char *)ptr - offset : NULL;
+}
+
+/*
+ * like `container_of', but allows returned value to be NULL
+ */
+#define container_of_or_null(ptr, type, member) \
+       (type *)container_of_or_null_offset(ptr, offsetof(type, member))
+
+/*
+ * like offsetof(), but takes a pointer to a a variable of type which
+ * contains @member, instead of a specified type.
+ * @ptr is subject to multiple evaluation since we can't rely on __typeof__
+ * everywhere.
+ */
+#if defined(__GNUC__) /* clang sets this, too */
+#define OFFSETOF_VAR(ptr, member) offsetof(__typeof__(*ptr), member)
+#else /* !__GNUC__ */
+#define OFFSETOF_VAR(ptr, member) \
+       ((uintptr_t)&(ptr)->member - (uintptr_t)(ptr))
+#endif /* !__GNUC__ */
+
 #endif
diff --git a/git-gui/README.md b/git-gui/README.md
new file mode 100644 (file)
index 0000000..5ce2122
--- /dev/null
@@ -0,0 +1,174 @@
+# Git GUI - A graphical user interface for Git
+
+Git GUI allows you to use the [Git source control management
+tools](https://git-scm.com/) via a GUI. This includes staging, committing,
+adding, pushing, etc. It can also be used as a blame viewer, a tree browser,
+and a citool (make exactly one commit before exiting and returning to shell).
+More details about Git GUI can be found in its manual page by either running
+`man git-gui`, or by visiting the [online manual
+page](https://git-scm.com/docs/git-gui).
+
+Git GUI was initially written by Shawn O. Pearce, and is distributed with the
+standard Git installation.
+
+# Building and installing
+
+You need to have the following dependencies installed before you begin:
+
+- Git
+- Tcl
+- Tk
+- wish
+- Gitk (needed for browsing history)
+- msgfmt
+
+Most of Git GUI is written in Tcl, so there is no compilation involved. Still,
+some things do need to be done (mostly some substitutions), so you do need to
+"build" it.
+
+You can build Git GUI using:
+
+```
+make
+```
+
+And then install it using:
+
+```
+make install
+```
+
+You probably need to have root/admin permissions to install.
+
+# Contributing
+
+The project is currently maintained by Pratyush Yadav over at
+https://github.com/prati0100/git-gui. Even though the project is hosted at
+GitHub, the development does not happen over GitHub Issues and Pull Requests.
+Instead, an email based workflow is used. The Git mailing list
+[git@vger.kernel.org](mailto:git@vger.kernel.org) is where the patches are
+discussed and reviewed.
+
+More information about the Git mailing list and instructions to subscribe can
+be found [here](https://git.wiki.kernel.org/index.php/GitCommunity).
+
+## Sending your changes
+
+Since the development happens over email, you need to send in your commits in
+text format. Commits can be converted to emails via the two tools provided by
+Git: `git-send-email` and `git-format-patch`.
+
+You can use `git-format-patch` to generate patches in mbox format from your
+commits that can then be sent via email. Let's say you are working on a branch
+called 'foo' that was created on top of 'master'. You can run:
+
+```
+git format-patch -o output_dir master..foo
+```
+
+to convert all the extra commits in 'foo' into a set of patches saved in the
+folder `output_dir`.
+
+If you are sending multiple patches, it is recommended to include a cover
+letter. A cover letter is an email explaining in brief what the series is
+supposed to do. A cover letter template can be generated by passing
+`--cover-letter` to `git-format-patch`.
+
+After you send your patches, you might get a review suggesting some changes.
+Make those changes, and re-send your patch(es) in reply to the first patch of
+your initial version. Also please mention the version of the patch. This can be
+done by passing `-v X` to `git-format-patch`, where 'X' is the version number
+of the patch(es).
+
+### Using git-send-email
+
+You can use `git-send-email` to send patches generated via `git-format-patch`.
+While you can directly send patches via `git-send-email`, it is recommended
+that you first use `git-format-patch` to generate the emails, audit them, and
+then send them via `git-send-email`.
+
+A pretty good guide to configuring and using `git-send-email` can be found
+[here](https://www.freedesktop.org/wiki/Software/PulseAudio/HowToUseGitSendEmail/)
+
+### Using your email client
+
+If your email client supports sending mbox format emails, you can use
+`git-format-patch` to get an mbox file for each commit, and then send them. If
+there is more than one patch in the series, then all patches after the first
+patch (or the cover letter) need to be sent as replies to the first.
+`git-send-email` does this by default.
+
+### Using GitGitGadget
+
+Since some people prefer a GitHub pull request based workflow, they can use
+[GitGitGadget](https://gitgitgadget.github.io/) to send in patches. The tool
+was originally written for sending patches to the Git project, but it now also
+supports sending patches for git-gui.
+
+Instructions for using GitGitGadget to send git-gui patches, courtesy of
+Johannes Schindelin:
+
+If you don't already have a fork of the [git/git](https://github.com/git/git)
+repo, you need to make one. Then clone your fork:
+
+```
+git clone https://github.com/<your-username>/git
+```
+
+Then add GitGitGadget as a remote:
+
+```
+git remote add gitgitgadget https://github.com/gitgitgadget/git
+```
+
+Then fetch the git-gui branch:
+
+```
+git fetch gitgitgadget git-gui/master
+```
+
+Then create a new branch based on git-gui/master:
+
+```
+git checkout -b <your-branch-name> git-gui/master
+```
+
+Make whatever commits you need to, push them to your fork, and then head over
+to https://github.com/gitgitgadget/git/pulls and open a Pull Request targeting
+git-gui/master.
+
+GitGitGadget will welcome you with a (hopefully) helpful message.
+
+## Signing off
+
+You need to sign off your commits before sending them to the list. You can do
+that by passing the `-s` option to `git-commit`. You can also use the "Sign
+Off" option in Git GUI.
+
+A sign-off is a simple 'Signed-off-by: A U Thor \<author@example.com\>' line at
+the end of the commit message, after your explanation of the commit.
+
+A sign-off means that you are legally allowed to send the code, and it serves
+as a certificate of origin. More information can be found at
+[developercertificate.org](https://developercertificate.org/).
+
+## Responding to review comments
+
+It is quite likely your patches will get review comments. Those comments are
+sent on the Git mailing list as replies to your patch, and you will usually be
+Cc'ed in those replies.
+
+You are expected to respond by either explaining your code further to convince
+the reviewer what you are doing is correct, or acknowledge the comments and
+re-send the patches with those comments addressed.
+
+Some tips for those not familiar with communication on a mailing list:
+
+- Use only plain text emails. No HTML at all.
+- Wrap lines at around 75 characters.
+- Do not send attachments. If you do need to send some files, consider using a
+  hosting service, and paste the link in your email.
+- Do not [top post](http://www.idallen.com/topposting.html).
+- Always "reply all". Keep all correspondents and the list in Cc. If you reply
+  directly to a reviewer, and not Cc the list, other people would not be able
+  to chime in.
index fd476b6..0d21f56 100755 (executable)
@@ -2736,10 +2736,18 @@ if {![is_bare]} {
 }
 
 if {[is_Windows]} {
+       # Use /git-bash.exe if available
+       set normalized [file normalize $::argv0]
+       regsub "/mingw../libexec/git-core/git-gui$" \
+               $normalized "/git-bash.exe" cmdLine
+       if {$cmdLine != $normalized && [file exists $cmdLine]} {
+               set cmdLine [list "Git Bash" $cmdLine &]
+       } else {
+               set cmdLine [list "Git Bash" bash --login -l &]
+       }
        .mbar.repository add command \
                -label [mc "Git Bash"] \
-               -command {eval exec [auto_execok start] \
-                                         [list "Git Bash" bash --login -l &]}
+               -command {eval exec [auto_execok start] $cmdLine}
 }
 
 if {[is_Windows] || ![is_bare]} {
@@ -3581,6 +3589,9 @@ $ui_diff tag conf d_s- \
 $ui_diff tag conf d< \
        -foreground orange \
        -font font_diffbold
+$ui_diff tag conf d| \
+       -foreground orange \
+       -font font_diffbold
 $ui_diff tag conf d= \
        -foreground orange \
        -font font_diffbold
index 958a0fa..871ad48 100644 (file)
@@ -270,19 +270,6 @@ proc show_other_diff {path w m cont_info} {
        }
 }
 
-proc get_conflict_marker_size {path} {
-       set size 7
-       catch {
-               set fd_rc [eval [list git_read check-attr "conflict-marker-size" -- $path]]
-               set ret [gets $fd_rc line]
-               close $fd_rc
-               if {$ret > 0} {
-                       regexp {.*: conflict-marker-size: (\d+)$} $line line size
-               }
-       }
-       return $size
-}
-
 proc start_show_diff {cont_info {add_opts {}}} {
        global file_states file_lists
        global is_3way_diff is_submodule_diff diff_active repo_config
@@ -298,7 +285,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
        set is_submodule_diff 0
        set diff_active 1
        set current_diff_header {}
-       set conflict_size [get_conflict_marker_size $path]
+       set conflict_size [gitattr $path conflict-marker-size 7]
 
        set cmd [list]
        if {$w eq $ui_index} {
@@ -360,6 +347,10 @@ proc start_show_diff {cont_info {add_opts {}}} {
        }
 
        set ::current_diff_inheader 1
+       # Detect pre-image lines of the diff3 conflict-style. They are just
+       # '++' lines which is not bijective. Thus, we need to maintain a state
+       # across lines.
+       set ::conflict_in_pre_image 0
        fconfigure $fd \
                -blocking 0 \
                -encoding [get_path_encoding $path] \
@@ -462,11 +453,23 @@ proc read_diff {fd conflict_size cont_info} {
                        {--} {set tags d_--}
                        {++} {
                                set regexp [string map [list %conflict_size $conflict_size]\
-                                                               {^\+\+([<>=]){%conflict_size}(?: |$)}]
+                                                               {^\+\+([<>=|]){%conflict_size}(?: |$)}]
                                if {[regexp $regexp $line _g op]} {
                                        set is_conflict_diff 1
                                        set line [string replace $line 0 1 {  }]
                                        set tags d$op
+
+                                       # The ||| conflict-marker marks the start of the pre-image.
+                                       # All those lines are also prefixed with '++'. Thus we need
+                                       # to maintain this state.
+                                       set ::conflict_in_pre_image [expr {$op eq {|}}]
+                               } elseif {$::conflict_in_pre_image} {
+                                       # This is a pre-image line. It is the one which both sides
+                                       # are based on. As it has also the '++' line start, it is
+                                       # normally shown as 'added'. Invert this to '--' to make
+                                       # it a 'removed' line.
+                                       set line [string replace $line 0 1 {--}]
+                                       set tags d_--
                                } else {
                                        set tags d_++
                                }
index 208651c..2f61153 100644 (file)
@@ -4,14 +4,15 @@
 #
 # しらいし ななこ <nanako3@bluebottle.com>, 2007.
 # Satoshi Yasushima <s.yasushima@gmail.com>, 2016.
+# KIDANI Akito <a.kid.1985@gmail.com>, 2019.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2016-05-27 17:52+0900\n"
-"PO-Revision-Date: 2016-06-22 12:50+0900\n"
-"Last-Translator: Satoshi Yasushima <s.yasushima@gmail.com>\n"
+"PO-Revision-Date: 2019-10-13 23:20+0900\n"
+"Last-Translator: KIDANI Akito <a.kid.1985@gmail.com>\n"
 "Language-Team: Japanese\n"
 "Language: ja\n"
 "MIME-Version: 1.0\n"
@@ -661,7 +662,7 @@ msgstr ""
 #: lib/merge.tcl:108
 #, tcl-format
 msgid "%s of %s"
-msgstr "%s の %s ブランチ"
+msgstr "%2$s の %1$s ブランチ"
 
 #: lib/merge.tcl:122
 #, tcl-format
@@ -956,7 +957,7 @@ msgstr "エラー: コマンドが失敗しました"
 #: lib/checkout_op.tcl:85
 #, tcl-format
 msgid "Fetching %s from %s"
-msgstr "%s から %s をフェッチしています"
+msgstr "%2$s から %1$s をフェッチしています"
 
 #: lib/checkout_op.tcl:133
 #, tcl-format
index f60e9b3..07ad4a5 100755 (executable)
@@ -370,7 +370,7 @@ push_stash () {
                        git diff-index -p --cached --binary HEAD -- "$@" |
                        git apply --index -R
                else
-                       git reset --hard -q
+                       git reset --hard -q --no-recurse-submodules
                fi
 
                if test "$keep_index" = "t" && test -n "$i_tree"
index db6eb50..e100d82 100644 (file)
@@ -484,7 +484,7 @@ function processBlameLines(lines) {
                        case 'previous':
                                curCommit.nprevious++;
                                // store only first 'previous' header
-                               if (!'previous' in curCommit) {
+                               if (!('previous' in curCommit)) {
                                        var parts = data.split(' ', 2);
                                        curCommit.previous    = parts[0];
                                        curCommit.file_parent = unquote(parts[1]);
diff --git a/grep.c b/grep.c
index 0bb4cbd..b7ae5a4 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -16,6 +16,20 @@ static int grep_source_is_binary(struct grep_source *gs,
 
 static struct grep_opt grep_defaults;
 
+#ifdef USE_LIBPCRE2
+static pcre2_general_context *pcre2_global_context;
+
+static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data)
+{
+       return malloc(size);
+}
+
+static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data)
+{
+       return free(pointer);
+}
+#endif
+
 static const char *color_grep_slots[] = {
        [GREP_COLOR_CONTEXT]        = "context",
        [GREP_COLOR_FILENAME]       = "filename",
@@ -150,12 +164,28 @@ int grep_config(const char *var, const char *value, void *cb)
  * Initialize one instance of grep_opt and copy the
  * default values from the template we read the configuration
  * information in an earlier call to git_config(grep_config).
+ *
+ * If using PCRE, make sure that the library is configured
+ * to use the same allocator as Git (e.g. nedmalloc on Windows).
+ *
+ * Any allocated memory needs to be released in grep_destroy().
  */
 void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix)
 {
        struct grep_opt *def = &grep_defaults;
        int i;
 
+#if defined(USE_LIBPCRE2)
+       if (!pcre2_global_context)
+               pcre2_global_context = pcre2_general_context_create(
+                                       pcre2_malloc, pcre2_free, NULL);
+#endif
+
+#ifdef USE_LIBPCRE1
+       pcre_malloc = malloc;
+       pcre_free = free;
+#endif
+
        memset(opt, 0, sizeof(*opt));
        opt->repo = repo;
        opt->prefix = prefix;
@@ -178,6 +208,13 @@ void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix
                color_set(opt->colors[i], def->colors[i]);
 }
 
+void grep_destroy(void)
+{
+#ifdef USE_LIBPCRE2
+       pcre2_general_context_free(pcre2_global_context);
+#endif
+}
+
 static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt)
 {
        /*
@@ -461,7 +498,6 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
        PCRE2_UCHAR errbuf[256];
        PCRE2_SIZE erroffset;
        int options = PCRE2_MULTILINE;
-       const uint8_t *character_tables = NULL;
        int jitret;
        int patinforet;
        size_t jitsizearg;
@@ -470,11 +506,15 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
 
        p->pcre2_compile_context = NULL;
 
+       /* pcre2_global_context is initialized in append_grep_pattern */
        if (opt->ignore_case) {
                if (!opt->ignore_locale && has_non_ascii(p->pattern)) {
-                       character_tables = pcre2_maketables(NULL);
+                       if (!pcre2_global_context)
+                               BUG("pcre2_global_context uninitialized");
+                       p->pcre2_tables = pcre2_maketables(pcre2_global_context);
                        p->pcre2_compile_context = pcre2_compile_context_create(NULL);
-                       pcre2_set_character_tables(p->pcre2_compile_context, character_tables);
+                       pcre2_set_character_tables(p->pcre2_compile_context,
+                                                       p->pcre2_tables);
                }
                options |= PCRE2_CASELESS;
        }
@@ -571,6 +611,7 @@ static void free_pcre2_pattern(struct grep_pat *p)
        pcre2_compile_context_free(p->pcre2_compile_context);
        pcre2_code_free(p->pcre2_pattern);
        pcre2_match_data_free(p->pcre2_match_data);
+       free((void *)p->pcre2_tables);
 }
 #else /* !USE_LIBPCRE2 */
 static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt)
diff --git a/grep.h b/grep.h
index 05dc1bb..811fd27 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -78,6 +78,7 @@ struct grep_pat {
        pcre2_code *pcre2_pattern;
        pcre2_match_data *pcre2_match_data;
        pcre2_compile_context *pcre2_compile_context;
+       const uint8_t *pcre2_tables;
        uint32_t pcre2_jit_on;
        unsigned fixed:1;
        unsigned is_fixed:1;
@@ -172,6 +173,7 @@ struct grep_opt {
 void init_grep_defaults(struct repository *);
 int grep_config(const char *var, const char *value, void *);
 void grep_init(struct grep_opt *, struct repository *repo, const char *prefix);
+void grep_destroy(void);
 void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt);
 
 void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);
index d42f01f..39c1311 100644 (file)
--- a/hashmap.c
+++ b/hashmap.c
@@ -140,8 +140,8 @@ static inline struct hashmap_entry **find_entry_ptr(const struct hashmap *map,
 }
 
 static int always_equal(const void *unused_cmp_data,
-                       const void *unused1,
-                       const void *unused2,
+                       const struct hashmap_entry *unused1,
+                       const struct hashmap_entry *unused2,
                        const void *unused_keydata)
 {
        return 0;
@@ -171,41 +171,49 @@ void hashmap_init(struct hashmap *map, hashmap_cmp_fn equals_function,
        map->do_count_items = 1;
 }
 
-void hashmap_free(struct hashmap *map, int free_entries)
+void hashmap_free_(struct hashmap *map, ssize_t entry_offset)
 {
        if (!map || !map->table)
                return;
-       if (free_entries) {
+       if (entry_offset >= 0) { /* called by hashmap_free_entries */
                struct hashmap_iter iter;
                struct hashmap_entry *e;
+
                hashmap_iter_init(map, &iter);
                while ((e = hashmap_iter_next(&iter)))
-                       free(e);
+                       /*
+                        * like container_of, but using caller-calculated
+                        * offset (caller being hashmap_free_entries)
+                        */
+                       free((char *)e - entry_offset);
        }
        free(map->table);
        memset(map, 0, sizeof(*map));
 }
 
-void *hashmap_get(const struct hashmap *map, const void *key, const void *keydata)
+struct hashmap_entry *hashmap_get(const struct hashmap *map,
+                               const struct hashmap_entry *key,
+                               const void *keydata)
 {
        return *find_entry_ptr(map, key, keydata);
 }
 
-void *hashmap_get_next(const struct hashmap *map, const void *entry)
+struct hashmap_entry *hashmap_get_next(const struct hashmap *map,
+                       const struct hashmap_entry *entry)
 {
-       struct hashmap_entry *e = ((struct hashmap_entry *) entry)->next;
+       struct hashmap_entry *e = entry->next;
        for (; e; e = e->next)
                if (entry_equals(map, entry, e, NULL))
                        return e;
        return NULL;
 }
 
-void hashmap_add(struct hashmap *map, void *entry)
+void hashmap_add(struct hashmap *map, struct hashmap_entry *entry)
 {
        unsigned int b = bucket(map, entry);
 
        /* add entry */
-       ((struct hashmap_entry *) entry)->next = map->table[b];
+       entry->next = map->table[b];
        map->table[b] = entry;
 
        /* fix size and rehash if appropriate */
@@ -216,7 +224,9 @@ void hashmap_add(struct hashmap *map, void *entry)
        }
 }
 
-void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata)
+struct hashmap_entry *hashmap_remove(struct hashmap *map,
+                                       const struct hashmap_entry *key,
+                                       const void *keydata)
 {
        struct hashmap_entry *old;
        struct hashmap_entry **e = find_entry_ptr(map, key, keydata);
@@ -238,7 +248,8 @@ void *hashmap_remove(struct hashmap *map, const void *key, const void *keydata)
        return old;
 }
 
-void *hashmap_put(struct hashmap *map, void *entry)
+struct hashmap_entry *hashmap_put(struct hashmap *map,
+                               struct hashmap_entry *entry)
 {
        struct hashmap_entry *old = hashmap_remove(map, entry, NULL);
        hashmap_add(map, entry);
@@ -252,7 +263,7 @@ void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter)
        iter->next = NULL;
 }
 
-void *hashmap_iter_next(struct hashmap_iter *iter)
+struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter)
 {
        struct hashmap_entry *current = iter->next;
        for (;;) {
@@ -275,10 +286,15 @@ struct pool_entry {
 };
 
 static int pool_entry_cmp(const void *unused_cmp_data,
-                         const struct pool_entry *e1,
-                         const struct pool_entry *e2,
-                         const unsigned char *keydata)
+                         const struct hashmap_entry *eptr,
+                         const struct hashmap_entry *entry_or_key,
+                         const void *keydata)
 {
+       const struct pool_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct pool_entry, ent);
+       e2 = container_of(entry_or_key, const struct pool_entry, ent);
+
        return e1->data != keydata &&
               (e1->len != e2->len || memcmp(e1->data, keydata, e1->len));
 }
@@ -290,18 +306,18 @@ const void *memintern(const void *data, size_t len)
 
        /* initialize string pool hashmap */
        if (!map.tablesize)
-               hashmap_init(&map, (hashmap_cmp_fn) pool_entry_cmp, NULL, 0);
+               hashmap_init(&map, pool_entry_cmp, NULL, 0);
 
        /* lookup interned string in pool */
-       hashmap_entry_init(&key, memhash(data, len));
+       hashmap_entry_init(&key.ent, memhash(data, len));
        key.len = len;
-       e = hashmap_get(&map, &key, data);
+       e = hashmap_get_entry(&map, &key, ent, data);
        if (!e) {
                /* not found: create it */
                FLEX_ALLOC_MEM(e, data, data, len);
-               hashmap_entry_init(e, key.ent.hash);
+               hashmap_entry_init(&e->ent, key.ent.hash);
                e->len = len;
-               hashmap_add(&map, e);
+               hashmap_add(&map, &e->ent);
        }
        return e->data;
 }
index 8424911..bd27015 100644 (file)
--- a/hashmap.h
+++ b/hashmap.h
@@ -13,7 +13,7 @@
  *
  * struct hashmap map;
  * struct long2string {
- *     struct hashmap_entry ent; // must be the first member!
+ *     struct hashmap_entry ent;
  *     long key;
  *     char value[FLEX_ARRAY];   // be careful with allocating on stack!
  * };
  * #define COMPARE_VALUE 1
  *
  * static int long2string_cmp(const void *hashmap_cmp_fn_data,
- *                            const struct long2string *e1,
- *                            const struct long2string *e2,
+ *                            const struct hashmap_entry *eptr,
+ *                            const struct hashmap_entry *entry_or_key,
  *                            const void *keydata)
  * {
  *     const char *string = keydata;
  *     unsigned flags = *(unsigned *)hashmap_cmp_fn_data;
+ *     const struct long2string *e1, *e2;
+ *
+ *     e1 = container_of(eptr, const struct long2string, ent);
+ *     e2 = container_of(entry_or_key, const struct long2string, ent);
  *
  *     if (flags & COMPARE_VALUE)
  *         return e1->key != e2->key ||
  *     char value[255], action[32];
  *     unsigned flags = 0;
  *
- *     hashmap_init(&map, (hashmap_cmp_fn) long2string_cmp, &flags, 0);
+ *     hashmap_init(&map, long2string_cmp, &flags, 0);
  *
  *     while (scanf("%s %ld %s", action, &key, value)) {
  *
  *         if (!strcmp("add", action)) {
  *             struct long2string *e;
  *             FLEX_ALLOC_STR(e, value, value);
- *             hashmap_entry_init(e, memhash(&key, sizeof(long)));
+ *             hashmap_entry_init(&e->ent, memhash(&key, sizeof(long)));
  *             e->key = key;
- *             hashmap_add(&map, e);
+ *             hashmap_add(&map, &e->ent);
  *         }
  *
  *         if (!strcmp("print_all_by_key", action)) {
  *             struct long2string k, *e;
- *             hashmap_entry_init(&k, memhash(&key, sizeof(long)));
+ *             hashmap_entry_init(&k->ent, memhash(&key, sizeof(long)));
  *             k.key = key;
  *
  *             flags &= ~COMPARE_VALUE;
- *             e = hashmap_get(&map, &k, NULL);
+ *             e = hashmap_get_entry(&map, &k, ent, NULL);
  *             if (e) {
  *                 printf("first: %ld %s\n", e->key, e->value);
- *                 while ((e = hashmap_get_next(&map, e)))
+ *                 while ((e = hashmap_get_next_entry(&map, e,
+ *                                              struct long2string, ent))) {
  *                     printf("found more: %ld %s\n", e->key, e->value);
+ *                 }
  *             }
  *         }
  *
  *         if (!strcmp("has_exact_match", action)) {
  *             struct long2string *e;
  *             FLEX_ALLOC_STR(e, value, value);
- *             hashmap_entry_init(e, memhash(&key, sizeof(long)));
+ *             hashmap_entry_init(&e->ent, memhash(&key, sizeof(long)));
  *             e->key = key;
  *
  *             flags |= COMPARE_VALUE;
- *             printf("%sfound\n", hashmap_get(&map, e, NULL) ? "" : "not ");
+ *             printf("%sfound\n",
+ *                    hashmap_get(&map, &e->ent, NULL) ? "" : "not ");
  *             free(e);
  *         }
  *
  *         if (!strcmp("has_exact_match_no_heap_alloc", action)) {
  *             struct long2string k;
- *             hashmap_entry_init(&k, memhash(&key, sizeof(long)));
+ *             hashmap_entry_init(&k->ent, memhash(&key, sizeof(long)));
  *             k.key = key;
  *
  *             flags |= COMPARE_VALUE;
- *             printf("%sfound\n", hashmap_get(&map, &k, value) ? "" : "not ");
+ *             printf("%sfound\n",
+ *                    hashmap_get(&map, &k->ent, value) ? "" : "not ");
  *         }
  *
  *         if (!strcmp("end", action)) {
- *             hashmap_free(&map, 1);
+ *             hashmap_free_entries(&map, struct long2string, ent);
  *             break;
  *         }
  *     }
@@ -133,7 +141,7 @@ static inline unsigned int oidhash(const struct object_id *oid)
 
 /*
  * struct hashmap_entry is an opaque structure representing an entry in the
- * hash table, which must be used as first member of user data structures.
+ * hash table.
  * Ideally it should be followed by an int-sized member to prevent unused
  * memory on 64-bit systems due to alignment.
  */
@@ -168,7 +176,8 @@ struct hashmap_entry {
  * The `hashmap_cmp_fn_data` entry is the pointer given in the init function.
  */
 typedef int (*hashmap_cmp_fn)(const void *hashmap_cmp_fn_data,
-                             const void *entry, const void *entry_or_key,
+                             const struct hashmap_entry *entry,
+                             const struct hashmap_entry *entry_or_key,
                              const void *keydata);
 
 /*
@@ -223,13 +232,20 @@ void hashmap_init(struct hashmap *map,
                         const void *equals_function_data,
                         size_t initial_size);
 
+/* internal function for freeing hashmap */
+void hashmap_free_(struct hashmap *map, ssize_t offset);
+
 /*
- * Frees a hashmap structure and allocated memory.
- *
- * If `free_entries` is true, each hashmap_entry in the map is freed as well
- * using stdlibs free().
+ * Frees a hashmap structure and allocated memory, leaves entries undisturbed
  */
-void hashmap_free(struct hashmap *map, int free_entries);
+#define hashmap_free(map) hashmap_free_(map, -1)
+
+/*
+ * Frees @map and all entries.  @type is the struct type of the entry
+ * where @member is the hashmap_entry struct used to associate with @map
+ */
+#define hashmap_free_entries(map, type, member) \
+       hashmap_free_(map, offsetof(type, member));
 
 /* hashmap_entry functions */
 
@@ -244,9 +260,9 @@ void hashmap_free(struct hashmap *map, int free_entries);
  * your structure was allocated with xmalloc(), you can just free(3) it,
  * and if it is on stack, you can just let it go out of scope).
  */
-static inline void hashmap_entry_init(void *entry, unsigned int hash)
+static inline void hashmap_entry_init(struct hashmap_entry *e,
+                                       unsigned int hash)
 {
-       struct hashmap_entry *e = entry;
        e->hash = hash;
        e->next = NULL;
 }
@@ -286,8 +302,9 @@ static inline unsigned int hashmap_get_size(struct hashmap *map)
  * If an entry with matching hash code is found, `key` and `keydata` are passed
  * to `hashmap_cmp_fn` to decide whether the entry matches the key.
  */
-void *hashmap_get(const struct hashmap *map, const void *key,
-                        const void *keydata);
+struct hashmap_entry *hashmap_get(const struct hashmap *map,
+                               const struct hashmap_entry *key,
+                               const void *keydata);
 
 /*
  * Returns the hashmap entry for the specified hash code and key data,
@@ -301,9 +318,10 @@ void *hashmap_get(const struct hashmap *map, const void *key,
  * `entry_or_key` parameter of `hashmap_cmp_fn` points to a hashmap_entry
  * structure that should not be used in the comparison.
  */
-static inline void *hashmap_get_from_hash(const struct hashmap *map,
-                                         unsigned int hash,
-                                         const void *keydata)
+static inline struct hashmap_entry *hashmap_get_from_hash(
+                                       const struct hashmap *map,
+                                       unsigned int hash,
+                                       const void *keydata)
 {
        struct hashmap_entry key;
        hashmap_entry_init(&key, hash);
@@ -318,7 +336,8 @@ static inline void *hashmap_get_from_hash(const struct hashmap *map,
  * `entry` is the hashmap_entry to start the search from, obtained via a previous
  * call to `hashmap_get` or `hashmap_get_next`.
  */
-void *hashmap_get_next(const struct hashmap *map, const void *entry);
+struct hashmap_entry *hashmap_get_next(const struct hashmap *map,
+                       const struct hashmap_entry *entry);
 
 /*
  * Adds a hashmap entry. This allows to add duplicate entries (i.e.
@@ -327,7 +346,7 @@ void *hashmap_get_next(const struct hashmap *map, const void *entry);
  * `map` is the hashmap structure.
  * `entry` is the entry to add.
  */
-void hashmap_add(struct hashmap *map, void *entry);
+void hashmap_add(struct hashmap *map, struct hashmap_entry *entry);
 
 /*
  * Adds or replaces a hashmap entry. If the hashmap contains duplicate
@@ -337,7 +356,20 @@ void hashmap_add(struct hashmap *map, void *entry);
  * `entry` is the entry to add or replace.
  * Returns the replaced entry, or NULL if not found (i.e. the entry was added).
  */
-void *hashmap_put(struct hashmap *map, void *entry);
+struct hashmap_entry *hashmap_put(struct hashmap *map,
+                               struct hashmap_entry *entry);
+
+/*
+ * Adds or replaces a hashmap entry contained within @keyvar,
+ * where @keyvar is a pointer to a struct containing a
+ * "struct hashmap_entry" @member.
+ *
+ * Returns the replaced pointer which is of the same type as @keyvar,
+ * or NULL if not found.
+ */
+#define hashmap_put_entry(map, keyvar, member) \
+       container_of_or_null_offset(hashmap_put(map, &(keyvar)->member), \
+                               OFFSETOF_VAR(keyvar, member))
 
 /*
  * Removes a hashmap entry matching the specified key. If the hashmap contains
@@ -346,8 +378,24 @@ void *hashmap_put(struct hashmap *map, void *entry);
  *
  * Argument explanation is the same as in `hashmap_get`.
  */
-void *hashmap_remove(struct hashmap *map, const void *key,
-               const void *keydata);
+struct hashmap_entry *hashmap_remove(struct hashmap *map,
+                                       const struct hashmap_entry *key,
+                                       const void *keydata);
+
+/*
+ * Removes a hashmap entry contained within @keyvar,
+ * where @keyvar is a pointer to a struct containing a
+ * "struct hashmap_entry" @member.
+ *
+ * See `hashmap_get` for an explanation of @keydata
+ *
+ * Returns the replaced pointer which is of the same type as @keyvar,
+ * or NULL if not found.
+ */
+#define hashmap_remove_entry(map, keyvar, member, keydata) \
+       container_of_or_null_offset( \
+                       hashmap_remove(map, &(keyvar)->member, keydata), \
+                       OFFSETOF_VAR(keyvar, member))
 
 /*
  * Returns the `bucket` an entry is stored in.
@@ -370,16 +418,74 @@ struct hashmap_iter {
 void hashmap_iter_init(struct hashmap *map, struct hashmap_iter *iter);
 
 /* Returns the next hashmap_entry, or NULL if there are no more entries. */
-void *hashmap_iter_next(struct hashmap_iter *iter);
+struct hashmap_entry *hashmap_iter_next(struct hashmap_iter *iter);
 
 /* Initializes the iterator and returns the first entry, if any. */
-static inline void *hashmap_iter_first(struct hashmap *map,
+static inline struct hashmap_entry *hashmap_iter_first(struct hashmap *map,
                struct hashmap_iter *iter)
 {
        hashmap_iter_init(map, iter);
        return hashmap_iter_next(iter);
 }
 
+/*
+ * returns the first entry in @map using @iter, where the entry is of
+ * @type (e.g. "struct foo") and @member is the name of the
+ * "struct hashmap_entry" in @type
+ */
+#define hashmap_iter_first_entry(map, iter, type, member) \
+       container_of_or_null(hashmap_iter_first(map, iter), type, member)
+
+/* internal macro for hashmap_for_each_entry */
+#define hashmap_iter_next_entry_offset(iter, offset) \
+       container_of_or_null_offset(hashmap_iter_next(iter), offset)
+
+/* internal macro for hashmap_for_each_entry */
+#define hashmap_iter_first_entry_offset(map, iter, offset) \
+       container_of_or_null_offset(hashmap_iter_first(map, iter), offset)
+
+/*
+ * iterate through @map using @iter, @var is a pointer to a type
+ * containing a @member which is a "struct hashmap_entry"
+ */
+#define hashmap_for_each_entry(map, iter, var, member) \
+       for (var = hashmap_iter_first_entry_offset(map, iter, \
+                                               OFFSETOF_VAR(var, member)); \
+               var; \
+               var = hashmap_iter_next_entry_offset(iter, \
+                                               OFFSETOF_VAR(var, member)))
+
+/*
+ * returns a pointer of type matching @keyvar, or NULL if nothing found.
+ * @keyvar is a pointer to a struct containing a
+ * "struct hashmap_entry" @member.
+ */
+#define hashmap_get_entry(map, keyvar, member, keydata) \
+       container_of_or_null_offset( \
+                               hashmap_get(map, &(keyvar)->member, keydata), \
+                               OFFSETOF_VAR(keyvar, member))
+
+#define hashmap_get_entry_from_hash(map, hash, keydata, type, member) \
+       container_of_or_null(hashmap_get_from_hash(map, hash, keydata), \
+                               type, member)
+/*
+ * returns the next equal pointer to @var, or NULL if not found.
+ * @var is a pointer of any type containing "struct hashmap_entry"
+ * @member is the name of the "struct hashmap_entry" field
+ */
+#define hashmap_get_next_entry(map, var, member) \
+       container_of_or_null_offset(hashmap_get_next(map, &(var)->member), \
+                               OFFSETOF_VAR(var, member))
+
+/*
+ * iterate @map starting from @var, where @var is a pointer of @type
+ * and @member is the name of the "struct hashmap_entry" field in @type
+ */
+#define hashmap_for_each_entry_from(map, var, member) \
+       for (; \
+               var; \
+               var = hashmap_get_next_entry(map, var, member))
+
 /*
  * Disable item counting and automatic rehashing when adding/removing items.
  *
index 0353f9f..822f326 100644 (file)
@@ -501,10 +501,10 @@ static void release_request(struct transfer_request *request)
        if (request == request_queue_head) {
                request_queue_head = request->next;
        } else {
-               while (entry->next != NULL && entry->next != request)
+               while (entry && entry->next != request)
                        entry = entry->next;
-               if (entry->next == request)
-                       entry->next = entry->next->next;
+               if (entry)
+                       entry->next = request->next;
        }
 
        free(request->url);
@@ -981,7 +981,7 @@ static int unlock_remote(struct remote_lock *lock)
                while (prev && prev->next != lock)
                        prev = prev->next;
                if (prev)
-                       prev->next = prev->next->next;
+                       prev->next = lock->next;
        }
 
        free(lock->owner);
index ec60715..11869ad 100644 (file)
@@ -4,30 +4,40 @@
  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
  */
 #include "cache.h"
-#include "config.h"
+#include "merge-recursive.h"
+
 #include "advice.h"
-#include "lockfile.h"
-#include "cache-tree.h"
-#include "object-store.h"
-#include "repository.h"
-#include "commit.h"
+#include "alloc.h"
+#include "attr.h"
 #include "blob.h"
 #include "builtin.h"
-#include "tree-walk.h"
+#include "cache-tree.h"
+#include "commit.h"
+#include "commit-reach.h"
+#include "config.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "dir.h"
+#include "ll-merge.h"
+#include "lockfile.h"
+#include "object-store.h"
+#include "repository.h"
+#include "revision.h"
+#include "string-list.h"
+#include "submodule.h"
 #include "tag.h"
-#include "alloc.h"
+#include "tree-walk.h"
 #include "unpack-trees.h"
-#include "string-list.h"
 #include "xdiff-interface.h"
-#include "ll-merge.h"
-#include "attr.h"
-#include "merge-recursive.h"
-#include "dir.h"
-#include "submodule.h"
-#include "revision.h"
-#include "commit-reach.h"
+
+struct merge_options_internal {
+       int call_depth;
+       int needed_rename_limit;
+       struct hashmap current_file_dir_set;
+       struct string_list df_conflict_file_set;
+       struct unpack_trees_options unpack_opts;
+       struct index_state orig_index;
+};
 
 struct path_hashmap_entry {
        struct hashmap_entry e;
@@ -35,14 +45,16 @@ struct path_hashmap_entry {
 };
 
 static int path_hashmap_cmp(const void *cmp_data,
-                           const void *entry,
-                           const void *entry_or_key,
+                           const struct hashmap_entry *eptr,
+                           const struct hashmap_entry *entry_or_key,
                            const void *keydata)
 {
-       const struct path_hashmap_entry *a = entry;
-       const struct path_hashmap_entry *b = entry_or_key;
+       const struct path_hashmap_entry *a, *b;
        const char *key = keydata;
 
+       a = container_of(eptr, const struct path_hashmap_entry, e);
+       b = container_of(entry_or_key, const struct path_hashmap_entry, e);
+
        if (ignore_case)
                return strcasecmp(a->path, key ? key : b->path);
        else
@@ -54,6 +66,24 @@ static unsigned int path_hash(const char *path)
        return ignore_case ? strihash(path) : strhash(path);
 }
 
+/*
+ * For dir_rename_entry, directory names are stored as a full path from the
+ * toplevel of the repository and do not include a trailing '/'.  Also:
+ *
+ *   dir:                original name of directory being renamed
+ *   non_unique_new_dir: if true, could not determine new_dir
+ *   new_dir:            final name of directory being renamed
+ *   possible_new_dirs:  temporary used to help determine new_dir; see comments
+ *                       in get_directory_renames() for details
+ */
+struct dir_rename_entry {
+       struct hashmap_entry ent;
+       char *dir;
+       unsigned non_unique_new_dir:1;
+       struct strbuf new_dir;
+       struct string_list possible_new_dirs;
+};
+
 static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
                                                      char *dir)
 {
@@ -61,18 +91,20 @@ static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap,
 
        if (dir == NULL)
                return NULL;
-       hashmap_entry_init(&key, strhash(dir));
+       hashmap_entry_init(&key.ent, strhash(dir));
        key.dir = dir;
-       return hashmap_get(hashmap, &key, NULL);
+       return hashmap_get_entry(hashmap, &key, ent, NULL);
 }
 
 static int dir_rename_cmp(const void *unused_cmp_data,
-                         const void *entry,
-                         const void *entry_or_key,
+                         const struct hashmap_entry *eptr,
+                         const struct hashmap_entry *entry_or_key,
                          const void *unused_keydata)
 {
-       const struct dir_rename_entry *e1 = entry;
-       const struct dir_rename_entry *e2 = entry_or_key;
+       const struct dir_rename_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct dir_rename_entry, ent);
+       e2 = container_of(entry_or_key, const struct dir_rename_entry, ent);
 
        return strcmp(e1->dir, e2->dir);
 }
@@ -85,34 +117,46 @@ static void dir_rename_init(struct hashmap *map)
 static void dir_rename_entry_init(struct dir_rename_entry *entry,
                                  char *directory)
 {
-       hashmap_entry_init(entry, strhash(directory));
+       hashmap_entry_init(&entry->ent, strhash(directory));
        entry->dir = directory;
        entry->non_unique_new_dir = 0;
        strbuf_init(&entry->new_dir, 0);
        string_list_init(&entry->possible_new_dirs, 0);
 }
 
+struct collision_entry {
+       struct hashmap_entry ent;
+       char *target_file;
+       struct string_list source_files;
+       unsigned reported_already:1;
+};
+
 static struct collision_entry *collision_find_entry(struct hashmap *hashmap,
                                                    char *target_file)
 {
        struct collision_entry key;
 
-       hashmap_entry_init(&key, strhash(target_file));
+       hashmap_entry_init(&key.ent, strhash(target_file));
        key.target_file = target_file;
-       return hashmap_get(hashmap, &key, NULL);
+       return hashmap_get_entry(hashmap, &key, ent, NULL);
 }
 
-static int collision_cmp(void *unused_cmp_data,
-                        const struct collision_entry *e1,
-                        const struct collision_entry *e2,
+static int collision_cmp(const void *unused_cmp_data,
+                        const struct hashmap_entry *eptr,
+                        const struct hashmap_entry *entry_or_key,
                         const void *unused_keydata)
 {
+       const struct collision_entry *e1, *e2;
+
+       e1 = container_of(eptr, const struct collision_entry, ent);
+       e2 = container_of(entry_or_key, const struct collision_entry, ent);
+
        return strcmp(e1->target_file, e2->target_file);
 }
 
 static void collision_init(struct hashmap *map)
 {
-       hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0);
+       hashmap_init(map, collision_cmp, NULL, 0);
 }
 
 static void flush_output(struct merge_options *opt)
@@ -284,7 +328,8 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,
 
 static int show(struct merge_options *opt, int v)
 {
-       return (!opt->call_depth && opt->verbosity >= v) || opt->verbosity >= 5;
+       return (!opt->priv->call_depth && opt->verbosity >= v) ||
+               opt->verbosity >= 5;
 }
 
 __attribute__((format (printf, 3, 4)))
@@ -295,7 +340,7 @@ static void output(struct merge_options *opt, int v, const char *fmt, ...)
        if (!show(opt, v))
                return;
 
-       strbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);
+       strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);
 
        va_start(ap, fmt);
        strbuf_vaddf(&opt->obuf, fmt, ap);
@@ -310,7 +355,7 @@ static void output_commit_title(struct merge_options *opt, struct commit *commit
 {
        struct merge_remote_desc *desc;
 
-       strbuf_addchars(&opt->obuf, ' ', opt->call_depth * 2);
+       strbuf_addchars(&opt->obuf, ' ', opt->priv->call_depth * 2);
        desc = merge_remote_util(commit);
        if (desc)
                strbuf_addf(&opt->obuf, "virtual %s\n", desc->name);
@@ -358,6 +403,11 @@ static int add_cacheinfo(struct merge_options *opt,
        return ret;
 }
 
+static inline int merge_detect_rename(struct merge_options *opt)
+{
+       return (opt->detect_renames >= 0) ? opt->detect_renames : 1;
+}
+
 static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
 {
        parse_tree(tree);
@@ -373,74 +423,43 @@ static int unpack_trees_start(struct merge_options *opt,
        struct tree_desc t[3];
        struct index_state tmp_index = { NULL };
 
-       memset(&opt->unpack_opts, 0, sizeof(opt->unpack_opts));
-       if (opt->call_depth)
-               opt->unpack_opts.index_only = 1;
+       memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
+       if (opt->priv->call_depth)
+               opt->priv->unpack_opts.index_only = 1;
        else
-               opt->unpack_opts.update = 1;
-       opt->unpack_opts.merge = 1;
-       opt->unpack_opts.head_idx = 2;
-       opt->unpack_opts.fn = threeway_merge;
-       opt->unpack_opts.src_index = opt->repo->index;
-       opt->unpack_opts.dst_index = &tmp_index;
-       opt->unpack_opts.aggressive = !merge_detect_rename(opt);
-       setup_unpack_trees_porcelain(&opt->unpack_opts, "merge");
+               opt->priv->unpack_opts.update = 1;
+       opt->priv->unpack_opts.merge = 1;
+       opt->priv->unpack_opts.head_idx = 2;
+       opt->priv->unpack_opts.fn = threeway_merge;
+       opt->priv->unpack_opts.src_index = opt->repo->index;
+       opt->priv->unpack_opts.dst_index = &tmp_index;
+       opt->priv->unpack_opts.aggressive = !merge_detect_rename(opt);
+       setup_unpack_trees_porcelain(&opt->priv->unpack_opts, "merge");
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
        init_tree_desc_from_tree(t+2, merge);
 
-       rc = unpack_trees(3, t, &opt->unpack_opts);
+       rc = unpack_trees(3, t, &opt->priv->unpack_opts);
        cache_tree_free(&opt->repo->index->cache_tree);
 
        /*
-        * Update opt->repo->index to match the new results, AFTER saving a copy
-        * in opt->orig_index.  Update src_index to point to the saved copy.
-        * (verify_uptodate() checks src_index, and the original index is
-        * the one that had the necessary modification timestamps.)
+        * Update opt->repo->index to match the new results, AFTER saving a
+        * copy in opt->priv->orig_index.  Update src_index to point to the
+        * saved copy.  (verify_uptodate() checks src_index, and the original
+        * index is the one that had the necessary modification timestamps.)
         */
-       opt->orig_index = *opt->repo->index;
+       opt->priv->orig_index = *opt->repo->index;
        *opt->repo->index = tmp_index;
-       opt->unpack_opts.src_index = &opt->orig_index;
+       opt->priv->unpack_opts.src_index = &opt->priv->orig_index;
 
        return rc;
 }
 
 static void unpack_trees_finish(struct merge_options *opt)
 {
-       discard_index(&opt->orig_index);
-       clear_unpack_trees_porcelain(&opt->unpack_opts);
-}
-
-struct tree *write_tree_from_memory(struct merge_options *opt)
-{
-       struct tree *result = NULL;
-       struct index_state *istate = opt->repo->index;
-
-       if (unmerged_index(istate)) {
-               int i;
-               fprintf(stderr, "BUG: There are unmerged index entries:\n");
-               for (i = 0; i < istate->cache_nr; i++) {
-                       const struct cache_entry *ce = istate->cache[i];
-                       if (ce_stage(ce))
-                               fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
-                                       (int)ce_namelen(ce), ce->name);
-               }
-               BUG("unmerged index entries in merge-recursive.c");
-       }
-
-       if (!istate->cache_tree)
-               istate->cache_tree = cache_tree();
-
-       if (!cache_tree_fully_valid(istate->cache_tree) &&
-           cache_tree_update(istate, 0) < 0) {
-               err(opt, _("error building trees"));
-               return NULL;
-       }
-
-       result = lookup_tree(opt->repo, &istate->cache_tree->oid);
-
-       return result;
+       discard_index(&opt->priv->orig_index);
+       clear_unpack_trees_porcelain(&opt->priv->unpack_opts);
 }
 
 static int save_files_dirs(const struct object_id *oid,
@@ -454,8 +473,8 @@ static int save_files_dirs(const struct object_id *oid,
        strbuf_addstr(base, path);
 
        FLEX_ALLOC_MEM(entry, path, base->buf, base->len);
-       hashmap_entry_init(entry, path_hash(entry->path));
-       hashmap_add(&opt->current_file_dir_set, entry);
+       hashmap_entry_init(&entry->e, path_hash(entry->path));
+       hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
 
        strbuf_setlen(base, baselen);
        return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
@@ -586,7 +605,7 @@ static void record_df_conflict_files(struct merge_options *opt,
         * If we're merging merge-bases, we don't want to bother with
         * any working directory changes.
         */
-       if (opt->call_depth)
+       if (opt->priv->call_depth)
                return;
 
        /* Ensure D/F conflicts are adjacent in the entries list. */
@@ -598,7 +617,7 @@ static void record_df_conflict_files(struct merge_options *opt,
        df_sorted_entries.cmp = string_list_df_name_compare;
        string_list_sort(&df_sorted_entries);
 
-       string_list_clear(&opt->df_conflict_file_set, 1);
+       string_list_clear(&opt->priv->df_conflict_file_set, 1);
        for (i = 0; i < df_sorted_entries.nr; i++) {
                const char *path = df_sorted_entries.items[i].string;
                int len = strlen(path);
@@ -614,7 +633,7 @@ static void record_df_conflict_files(struct merge_options *opt,
                    len > last_len &&
                    memcmp(path, last_file, last_len) == 0 &&
                    path[last_len] == '/') {
-                       string_list_insert(&opt->df_conflict_file_set, last_file);
+                       string_list_insert(&opt->priv->df_conflict_file_set, last_file);
                }
 
                /*
@@ -681,8 +700,8 @@ static void update_entry(struct stage_data *entry,
 static int remove_file(struct merge_options *opt, int clean,
                       const char *path, int no_wd)
 {
-       int update_cache = opt->call_depth || clean;
-       int update_working_directory = !opt->call_depth && !no_wd;
+       int update_cache = opt->priv->call_depth || clean;
+       int update_working_directory = !opt->priv->call_depth && !no_wd;
 
        if (update_cache) {
                if (remove_file_from_index(opt->repo->index, path))
@@ -712,7 +731,9 @@ static void add_flattened_path(struct strbuf *out, const char *s)
                        out->buf[i] = '_';
 }
 
-static char *unique_path(struct merge_options *opt, const char *path, const char *branch)
+static char *unique_path(struct merge_options *opt,
+                        const char *path,
+                        const char *branch)
 {
        struct path_hashmap_entry *entry;
        struct strbuf newpath = STRBUF_INIT;
@@ -723,16 +744,16 @@ static char *unique_path(struct merge_options *opt, const char *path, const char
        add_flattened_path(&newpath, branch);
 
        base_len = newpath.len;
-       while (hashmap_get_from_hash(&opt->current_file_dir_set,
+       while (hashmap_get_from_hash(&opt->priv->current_file_dir_set,
                                     path_hash(newpath.buf), newpath.buf) ||
-              (!opt->call_depth && file_exists(newpath.buf))) {
+              (!opt->priv->call_depth && file_exists(newpath.buf))) {
                strbuf_setlen(&newpath, base_len);
                strbuf_addf(&newpath, "_%d", suffix++);
        }
 
        FLEX_ALLOC_MEM(entry, path, newpath.buf, newpath.len);
-       hashmap_entry_init(entry, path_hash(entry->path));
-       hashmap_add(&opt->current_file_dir_set, entry);
+       hashmap_entry_init(&entry->e, path_hash(entry->path));
+       hashmap_add(&opt->priv->current_file_dir_set, &entry->e);
        return strbuf_detach(&newpath, NULL);
 }
 
@@ -775,7 +796,7 @@ static int dir_in_way(struct index_state *istate, const char *path,
 static int was_tracked_and_matches(struct merge_options *opt, const char *path,
                                   const struct diff_filespec *blob)
 {
-       int pos = index_name_pos(&opt->orig_index, path, strlen(path));
+       int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));
        struct cache_entry *ce;
 
        if (0 > pos)
@@ -783,7 +804,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,
                return 0;
 
        /* See if the file we were tracking before matches */
-       ce = opt->orig_index.cache[pos];
+       ce = opt->priv->orig_index.cache[pos];
        return (oid_eq(&ce->oid, &blob->oid) && ce->ce_mode == blob->mode);
 }
 
@@ -792,7 +813,7 @@ static int was_tracked_and_matches(struct merge_options *opt, const char *path,
  */
 static int was_tracked(struct merge_options *opt, const char *path)
 {
-       int pos = index_name_pos(&opt->orig_index, path, strlen(path));
+       int pos = index_name_pos(&opt->priv->orig_index, path, strlen(path));
 
        if (0 <= pos)
                /* we were tracking this path before the merge */
@@ -849,12 +870,12 @@ static int was_dirty(struct merge_options *opt, const char *path)
        struct cache_entry *ce;
        int dirty = 1;
 
-       if (opt->call_depth || !was_tracked(opt, path))
+       if (opt->priv->call_depth || !was_tracked(opt, path))
                return !dirty;
 
-       ce = index_file_exists(opt->unpack_opts.src_index,
+       ce = index_file_exists(opt->priv->unpack_opts.src_index,
                               path, strlen(path), ignore_case);
-       dirty = verify_uptodate(ce, &opt->unpack_opts) != 0;
+       dirty = verify_uptodate(ce, &opt->priv->unpack_opts) != 0;
        return dirty;
 }
 
@@ -864,8 +885,8 @@ static int make_room_for_path(struct merge_options *opt, const char *path)
        const char *msg = _("failed to create path '%s'%s");
 
        /* Unlink any D/F conflict files that are in the way */
-       for (i = 0; i < opt->df_conflict_file_set.nr; i++) {
-               const char *df_path = opt->df_conflict_file_set.items[i].string;
+       for (i = 0; i < opt->priv->df_conflict_file_set.nr; i++) {
+               const char *df_path = opt->priv->df_conflict_file_set.items[i].string;
                size_t pathlen = strlen(path);
                size_t df_pathlen = strlen(df_path);
                if (df_pathlen < pathlen &&
@@ -875,7 +896,7 @@ static int make_room_for_path(struct merge_options *opt, const char *path)
                               _("Removing %s to make room for subdirectory\n"),
                               df_path);
                        unlink(df_path);
-                       unsorted_string_list_delete_item(&opt->df_conflict_file_set,
+                       unsorted_string_list_delete_item(&opt->priv->df_conflict_file_set,
                                                         i, 0);
                        break;
                }
@@ -916,7 +937,7 @@ static int update_file_flags(struct merge_options *opt,
 {
        int ret = 0;
 
-       if (opt->call_depth)
+       if (opt->priv->call_depth)
                update_wd = 0;
 
        if (update_wd) {
@@ -935,9 +956,11 @@ static int update_file_flags(struct merge_options *opt,
                }
 
                buf = read_object_file(&contents->oid, &type, &size);
-               if (!buf)
-                       return err(opt, _("cannot read object %s '%s'"),
-                                  oid_to_hex(&contents->oid), path);
+               if (!buf) {
+                       ret = err(opt, _("cannot read object %s '%s'"),
+                                 oid_to_hex(&contents->oid), path);
+                       goto free_buf;
+               }
                if (type != OBJ_BLOB) {
                        ret = err(opt, _("blob expected for %s '%s'"),
                                  oid_to_hex(&contents->oid), path);
@@ -945,7 +968,8 @@ static int update_file_flags(struct merge_options *opt,
                }
                if (S_ISREG(contents->mode)) {
                        struct strbuf strbuf = STRBUF_INIT;
-                       if (convert_to_working_tree(opt->repo->index, path, buf, size, &strbuf)) {
+                       if (convert_to_working_tree(opt->repo->index,
+                                                   path, buf, size, &strbuf)) {
                                free(buf);
                                size = strbuf.len;
                                buf = strbuf_detach(&strbuf, NULL);
@@ -998,7 +1022,7 @@ static int update_file(struct merge_options *opt,
                       const char *path)
 {
        return update_file_flags(opt, contents, path,
-                                opt->call_depth || clean, !opt->call_depth);
+                                opt->priv->call_depth || clean, !opt->priv->call_depth);
 }
 
 /* Low level file merging, update and removal */
@@ -1020,22 +1044,22 @@ static int merge_3way(struct merge_options *opt,
 {
        mmfile_t orig, src1, src2;
        struct ll_merge_options ll_opts = {0};
-       char *base_name, *name1, *name2;
+       char *base, *name1, *name2;
        int merge_status;
 
        ll_opts.renormalize = opt->renormalize;
        ll_opts.extra_marker_size = extra_marker_size;
        ll_opts.xdl_opts = opt->xdl_opts;
 
-       if (opt->call_depth) {
+       if (opt->priv->call_depth) {
                ll_opts.virtual_ancestor = 1;
                ll_opts.variant = 0;
        } else {
                switch (opt->recursive_variant) {
-               case MERGE_RECURSIVE_OURS:
+               case MERGE_VARIANT_OURS:
                        ll_opts.variant = XDL_MERGE_FAVOR_OURS;
                        break;
-               case MERGE_RECURSIVE_THEIRS:
+               case MERGE_VARIANT_THEIRS:
                        ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
                        break;
                default:
@@ -1044,16 +1068,13 @@ static int merge_3way(struct merge_options *opt,
                }
        }
 
-       assert(a->path && b->path);
-       if (strcmp(a->path, b->path) ||
-           (opt->ancestor != NULL && strcmp(a->path, o->path) != 0)) {
-               base_name = opt->ancestor == NULL ? NULL :
-                       mkpathdup("%s:%s", opt->ancestor, o->path);
+       assert(a->path && b->path && o->path && opt->ancestor);
+       if (strcmp(a->path, b->path) || strcmp(a->path, o->path) != 0) {
+               base  = mkpathdup("%s:%s", opt->ancestor, o->path);
                name1 = mkpathdup("%s:%s", branch1, a->path);
                name2 = mkpathdup("%s:%s", branch2, b->path);
        } else {
-               base_name = opt->ancestor == NULL ? NULL :
-                       mkpathdup("%s", opt->ancestor);
+               base  = mkpathdup("%s", opt->ancestor);
                name1 = mkpathdup("%s", branch1);
                name2 = mkpathdup("%s", branch2);
        }
@@ -1062,11 +1083,11 @@ static int merge_3way(struct merge_options *opt,
        read_mmblob(&src1, &a->oid);
        read_mmblob(&src2, &b->oid);
 
-       merge_status = ll_merge(result_buf, a->path, &orig, base_name,
+       merge_status = ll_merge(result_buf, a->path, &orig, base,
                                &src1, name1, &src2, name2,
                                opt->repo->index, &ll_opts);
 
-       free(base_name);
+       free(base);
        free(name1);
        free(name2);
        free(orig.ptr);
@@ -1161,7 +1182,7 @@ static int merge_submodule(struct merge_options *opt,
        struct object_array merges;
 
        int i;
-       int search = !opt->call_depth;
+       int search = !opt->priv->call_depth;
 
        /* store a in result in case we fail */
        oidcpy(result, a);
@@ -1345,15 +1366,15 @@ static int merge_mode_and_contents(struct merge_options *opt,
                                                        &b->oid);
                } else if (S_ISLNK(a->mode)) {
                        switch (opt->recursive_variant) {
-                       case MERGE_RECURSIVE_NORMAL:
+                       case MERGE_VARIANT_NORMAL:
      &nbs