Skip to content

Rebase to Git v2.24.0 #2386

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 337 commits into from
Nov 4, 2019
Merged

Rebase to Git v2.24.0 #2386

merged 337 commits into from
Nov 4, 2019

Conversation

dscho
Copy link
Member

@dscho dscho commented Nov 4, 2019

I will follow-up with the range-diff plus discussion about it.

dscho added 30 commits November 4, 2019 07:33
When skipping a hunk that adds a different number of lines than it
removes, we need to adjust the subsequent hunk headers of non-skipped
hunks: in pathological cases, the context is not enough to determine
precisely where the patch should be applied.

This problem was identified in 23fea4c (t3701: add failing test for
pathological context lines, 2018-03-01) and fixed in the Perl version in
fecc6f3 (add -p: adjust offsets of subsequent hunks when one is
skipped, 2018-03-01).

And this patch fixes it in the C version of `git add -p`.

In contrast to the Perl version, we try to keep the extra text on the
hunk header (which typically contains the signature of the function
whose code is changed in the hunk) intact.

Note: while the C version does not support staging mode changes at this
stage, we already prepare for this by simply skipping the hunk header if
both old and new offset is 0 (this cannot happen for regular hunks, and
we will use this as an indicator that we are looking at a special hunk).

Likewise, we already prepare for hunk splitting by handling the absence
of extra text in the hunk header gracefully: only the first split hunk
will have that text, the others will not (indicated by an empty extra
text start/end range). Preparing for hunk splitting already at this
stage avoids an indentation change of the entire hunk header-printing
block later, and is almost as easy to review as without that handling.

Signed-off-by: Johannes Schindelin <[email protected]>
Yes, yes, this is supposed to be only a band-aid option for `git add -p`
not Doing The Right Thing. But as long as we carry the `--allow-overlap`
option, we might just as well get it right.

This fixes the case where one hunk inserts a line before the first one,
and a hunk whose context overlaps with the first one's appends a line at
the end.

Signed-off-by: Johannes Schindelin <[email protected]>
... just like the Perl version ;-)

Note that this requires the `get_add_i_color()` function being defined
globally, which is the entire reason why we gave it such a descriptive
name in the first place.

Signed-off-by: Johannes Schindelin <[email protected]>
…ailed

... just like the Perl version currently does...

Signed-off-by: Johannes Schindelin <[email protected]>
For simplicity, the initial implementation in C handled only a single
modified file. Now it handles an arbitrary number of files.

Signed-off-by: Johannes Schindelin <[email protected]>
This addresses the same problem as 24ab81a (add-interactive: handle
deletion of empty files, 2009-10-27), although in a different way: we
not only stick the "deleted file" line into its own pseudo hunk, but
also the entire remainder (if any) of the same diff.

That way, we do not have to play any funny games with regards to
coalescing the diff after the user selected what (possibly pseudo-)hunks
to stage.

Signed-off-by: Johannes Schindelin <[email protected]>
This imitates the way the Perl version treats mode changes: it offers
the mode change up for the user to decide, as if it was a diff hunk.

In contrast to the Perl version, we make use of the fact that the mode
line is the first hunk, and explicitly strip out that line from the diff
header if that "hunk" was not selected to be applied, and skipping that
hunk while coalescing the diff. The Perl version plays some kind of diff
line lego instead.

Signed-off-by: Johannes Schindelin <[email protected]>
Just like the Perl version, we now helpfully ask the user whether they
want to stage a mode change, or a deletion.

Note that we define the prompts in an array, in preparation for a later
patch that changes those prompts to yet different versions for `git
reset -p`, `git stash -p` and `git checkout -p` (which all call the `git
add -p` machinery to do the actual work).

Signed-off-by: Johannes Schindelin <[email protected]>
If this developer's workflow is any indication, then this is *the* most
useful feature of Git's interactive `add `command.

Note: once again, this is not a verbatim conversion from the Perl code
to C: the `hunk_splittable()` function, for example, essentially did all
the work of splitting the hunk, just to find out whether more than one
hunk would have been the result (and then tossed that result into the
trash). In C we instead count the number of resulting hunks (without
actually doing the work of splitting, but just counting the transitions
from non-context lines to context lines), and store that information
with the hunk, and we do that *while* parsing the diff in the first
place.

Another deviation: the built-in `git add -p` was designed with a single
strbuf holding the diff (and another one holding the colored diff, if
that one was asked for) in mind, and hunks essentially store just the
start and end offsets pointing into that strbuf. As a consequence, when
we split hunks, we now use a special mode where the hunk header is
generated dynamically, and only the rest of the hunk is stored using
such start/end offsets. This way, we also avoid the frequent
formatting/re-parsing of the hunk header of the Perl version.

Signed-off-by: Johannes Schindelin <[email protected]>
This is considered "the right thing to do", according to 933e44d
("add -p": work-around an old laziness that does not coalesce hunks,
2011-04-06).

Note: we cannot simply modify the hunks while merging them; Once we
implement hunk editing, we will call `reassemble_patch()` whenever a
hunk is edited, therefore we must not modify the hunks (because the user
might e.g. hit `K` and change their mind whether to stage the previous
hunk).

Signed-off-by: Johannes Schindelin <[email protected]>
Just like `git add --edit` allows the user to edit the diff before it is
being applied to the index, this feature allows the user to edit the
diff *hunk*.

Naturally, it gets a bit more complicated here because the result has
to play well with the remaining hunks of the overall diff. Therefore,
we have to do a loop in which we let the user edit the hunk, then test
whether the result would work, and if not, drop the edits and let the
user decide whether to try editing the hunk again.

Note: in contrast to the Perl version, we use the same diff
"coalescing" (i.e. merging overlapping hunks into a single one) also for
the check after editing, and we introduce a new flag for that purpose
that asks the `reassemble_patch()` function to pretend that all hunks
were selected for use.

This allows us to continue to run `git apply` *without* the
`--allow-overlap` option (unlike the Perl version), and it also fixes
two known breakages in `t3701-add-interactive.sh` (which we cannot mark
as resolved so far because the Perl script version is still the default
and continues to have those breakages).

Signed-off-by: Johannes Schindelin <[email protected]>
The Perl script backing `git add -p` is used not only for that command,
but also for `git stash -p`, `git reset -p` and `git checkout -p`.

In preparation for teaching the C version of `git add -p` to support
also the latter commands, let's abstract away what is "stage" specific
into a dedicated data structure describing the differences between the
patch modes.

As we prepare for calling the built-in `git add -p` in
`run_add_interactive()` via code paths that have not let `add_config()`
do its work, we have to make sure to re-parse the config using that
function in those cases.

Finally, please note that the Perl version tries to make sure that the
diffs are only generated for the modified files. This is not actually
necessary, as the calls to Git's diff machinery already perform that
work, and perform it well. This makes it unnecessary to port the
`FILTER` field of the `%patch_modes` struct, as well as the
`get_diff_reference()` function.

Signed-off-by: Johannes Schindelin <[email protected]>
With this patch, it is now possible to see a summary of the available
hunks and to navigate between them (by number).

A test is added to verify that this behavior matches the one of the Perl
version of `git add -p`.

Signed-off-by: Johannes Schindelin <[email protected]>
The `git stash` and `git reset` commands support a `--patch` option, and
both simply hand off to `git add -p` to perform that work. Let's teach
the built-in version of `git add -p` do perform that work, too.

Signed-off-by: Johannes Schindelin <[email protected]>
This patch implements the hunk searching feature in the C version of
`git add -p`.

A test is added to verify that this behavior matches the one of the Perl
version of `git add -p`.

Note that this involves a change of behavior: the Perl version uses (of
course) the Perl flavor of regular expressions, while this patch uses
the regcomp()/regexec(), i.e. POSIX extended regular expressions. In
practice, this behavior change is unlikely to matter.

Signed-off-by: Johannes Schindelin <[email protected]>
As `git add` traditionally did not expose the `--patch=<mode>` modes via
command-line options, the scripted version of `git stash` had to call
`git add--interactive` directly.

But this prevents the built-in `add -p` from kicking in, as
`add--interactive` is the Perl script.

So let's introduce support for an optional `<mode>` argument in `git add
--patch[=<mode>]`, and use that in the scripted version of `git stash
-p`, so that the built-in interactive add can do its job if configured.

Signed-off-by: Johannes Schindelin <[email protected]>
This command is actually very similar to the 'd' ("do not stage this
hunk or any of the later hunks in the file") command: it just does
something on top, namely leave the loop and return a value indicating
that we're quittin'.

Signed-off-by: Johannes Schindelin <[email protected]>
The scripted version of `git stash` called directly into the Perl script
`git-add--interactive.perl`, and this was faithfully converted to C.

However, we have a much better way to do this now: call `git add
--patch=<mode>`, which incidentally also respects the config setting
`add.interactive.useBuiltin`.

Let's do this.

Signed-off-by: Johannes Schindelin <[email protected]>
When displaying the only hunk in a file's diff, the prompt already
excludes the commands to navigate to the previous/next hunk.

Let's also let the `?` command show only the help lines corresponding to
the commands that are displayed in the prompt.

Signed-off-by: Johannes Schindelin <[email protected]>
This patch teaches the built-in `git add -p` machinery all the tricks it
needs to know in order to act as the work horse for `git checkout -p`.

Apart from the minor changes (slightly reworded messages, different
`diff` and `apply --check` invocations), it requires a new function to
actually apply the changes, as `git checkout -p` is a bit special in
that respect: when the desired changes do not apply to the index, but
apply to the work tree, Git does not fail straight away, but asks the
user whether to apply the changes to the worktree at least.

Signed-off-by: Johannes Schindelin <[email protected]>
This patch will make `git add -p` show "No changes." or "Only binary
files changed." in that case.

Signed-off-by: Johannes Schindelin <[email protected]>
The Perl version supports post-processing the colored diff (that is
generated in addition to the uncolored diff, intended to offer a
prettier user experience) by a command configured via that config
setting, and now the built-in version does that, too.

Signed-off-by: Johannes Schindelin <[email protected]>
The Perl version of `git add -p` reads the config setting
`diff.algorithm` and if set, uses it to generate the diff using the
specified algorithm.

This patch ports that functionality to the C version.

To make sure that this works as intended, we add a regression test case
that tries to specify a bogus diff algorithm and then verifies that `git
diff-files` produced the expected error message.

Note: In that new test case, we actually ignore the exit code of `git
add -p`. The reason is that the C version exits with failure (as one
might expect), but the Perl version does not.

In fact, the Perl version continues happily after the uncolored diff
failed, trying to generate the colored diff, still not catching the
problem, and then it pretends to have succeeded (with exit code 0).

This is arguably a bug in the Perl version, and fixing it is safely
outside the scope of this patch.

Signed-off-by: Johannes Schindelin <[email protected]>
We are about to introduce the function `enable_non_canonical()`, which
shares almost the complete code with `disable_echo()`.

Let's prepare for that, by refactoring out that shared code.

Signed-off-by: Johannes Schindelin <[email protected]>
Git for Windows' Git Bash runs in MinTTY by default, which does not have
a Win32 Console instance, but uses MSYS2 pseudo terminals instead.

This is a problem, as Git for Windows does not want to use the MSYS2
emulation layer for Git itself, and therefore has no direct way to
interact with that pseudo terminal.

As a workaround, use the `stty` utility (which is included in Git for
Windows, and which *is* an MSYS2 program, so it knows how to deal with
the pseudo terminal).

Note: If Git runs in a regular CMD or PowerShell window, there *is* a
regular Win32 Console to work with. This is not a problem for the MSYS2
`stty`: it copes with this scenario just fine.

Also note that we introduce support for more bits than would be
necessary for a mere `disable_echo()` here, in preparation for the
upcoming `enable_non_canonical()` function.

Signed-off-by: Johannes Schindelin <[email protected]>
Typically, input on the command-line is line-based. It is actually not
really easy to get single characters (or better put: keystrokes).

We provide two implementations here:

- One that handles `/dev/tty` based systems as well as native Windows.
  The former uses the `tcsetattr()` function to put the terminal into
  "raw mode", which allows us to read individual keystrokes, one by one.
  The latter uses `stty.exe` to do the same, falling back to direct
  Win32 Console access.

  Thanks to the refactoring leading up to this commit, this is a single
  function, with the platform-specific details hidden away in
  conditionally-compiled code blocks.

- A fall-back which simply punts and reads back an entire line.

Note that the function writes the keystroke into an `strbuf` rather than
a `char`, in preparation for reading Escape sequences (e.g. when the
user hit an arrow key). This is also required for UTF-8 sequences in
case the keystroke corresponds to a non-ASCII letter.

Signed-off-by: Johannes Schindelin <[email protected]>
This is a straight-forward port of 2f0896e (restore: support
--patch, 2019-04-25) which added support for `git restore -p`.

Signed-off-by: Johannes Schindelin <[email protected]>
The Perl version of `git add -p` supports this config setting to allow
users to input commands via single characters (as opposed to having to
press the <Enter> key afterwards).

This is an opt-in feature because it requires Perl packages
(Term::ReadKey and Term::Cap, where it tries to handle an absence of the
latter package gracefully) to work. Note that at least on Ubuntu, that
Perl package is not installed by default (it needs to be installed via
`sudo apt-get install libterm-readkey-perl`), so this feature is
probably not used a whole lot.

In C, we obviously do not have these packages available, but we just
introduced `read_single_keystroke()` that is similar to what
Term::ReadKey provides, and we use that here.

Signed-off-by: Johannes Schindelin <[email protected]>
The built-in `git add -i` machinery obviously has its `the_repository`
structure initialized at the point where `cmd_commit()` calls it, and
therefore does not look at the environment variable `GIT_INDEX_FILE`.

But it has to, because the index was already locked, and we want to ask
the interactive add machinery to work on the `index.lock` file instead
of the `index` file.

Technically, we could teach `run_add_i()` (and `run_add_p()`) to look
specifically at that environment variable, but the entire idea of
passing in a parameter of type `struct repository *` is to allow working
on multiple repositories (and their index files) independently.

So let's instead override the `index_file` field of that structure
temporarily.

Signed-off-by: Johannes Schindelin <[email protected]>
This recapitulates part of b5cc003 (add -i: ignore terminal escape
sequences, 2011-05-17):

    add -i: ignore terminal escape sequences

    On the author's terminal, the up-arrow input sequence is ^[[A, and
    thus fat-fingering an up-arrow into 'git checkout -p' is quite
    dangerous: git-add--interactive.perl will ignore the ^[ and [
    characters and happily treat A as "discard everything".

    As a band-aid fix, use Term::Cap to get all terminal capabilities.
    Then use the heuristic that any capability value that starts with ^[
    (i.e., \e in perl) must be a key input sequence.  Finally, given an
    input that starts with ^[, read more characters until we have read a
    full escape sequence, then return that to the caller.  We use a
    timeout of 0.5 seconds on the subsequent reads to avoid getting stuck
    if the user actually input a lone ^[.

    Since none of the currently recognized keys start with ^[, the net
    result is that the sequence as a whole will be ignored and the help
    displayed.

Note that we leave part for later which uses "Term::Cap to get all
terminal capabilities", for several reasons:

1. it is actually not really necessary, as the timeout of 0.5 seconds
   should be plenty sufficient to catch Escape sequences,

2. it is cleaner to keep the change to special-case Escape sequences
   separate from the change that reads all terminal capabilities to
   speed things up, and

3. in practice, relying on the terminal capabilities is a bit overrated,
   as the information could be incomplete, or plain wrong. For example,
   in this developer's tmux sessions, the terminal capabilities claim
   that the "cursor up" sequence is ^[M, but the actual sequence
   produced by the "cursor up" key is ^[[A.

Signed-off-by: Johannes Schindelin <[email protected]>
dscho and others added 27 commits November 4, 2019 07:35
…er/fscache_nfd

fscache: add not-found directory cache to fscache
…er/add_preload_fscache

add: use preload-index and fscache for performance
…xcludes_with_fscache

dir.c: make add_excludes aware of fscache during status
fetch-pack.c: enable fscache for stats under .git/objects
…t_flush

checkout.c: enable fscache for checkout again

Signed-off-by: Johannes Schindelin <[email protected]>
…_index

Enable the filesystem cache (fscache) in refresh_index().
…-gfw

fscache: use FindFirstFileExW to avoid retrieving the short name
…ter-status-gfw

status: disable and free fscache at the end of the status command
…e-gfw

fscache: add GIT_TEST_FSCACHE support
…ter-add-gfw

At the end of the add command, disable and free the fscache
…ics-gfw

fscache: add fscache hit statistics
This brings substantial wins in performance because the FSCache is now
per-thread, being merged to the primary thread only at the end, so we do
not have to lock (except while merging).

Signed-off-by: Johannes Schindelin <[email protected]>
…safe-enable-gfw

fscache: make fscache_enable() thread safe
…DirectoryFile-gfw

 fscache: teach fscache to use NtQueryDirectoryFile
When updating the skip-worktree bits in the index to align with new
values in a sparse-checkout file, Git scans the entire working
directory with lstat() calls. In a sparse-checkout, many of these
lstat() calls are for paths that do not exist.

Enable the fscache feature during this scan.

In a local test of a repo with ~2.2 million paths, updating the index
with `git read-tree -m -u HEAD` with a sparse-checkout file containing
only `/.gitattributes` improved from 2-3 minutes to 15-20 seconds.

More work could be done to stop running lstat() calls when recursing
into directories that are known to not exist.
We already avoid traversing NTFS junction points in `git clean -dfx`.
With this topic branch, we do that when the FSCache is enabled, too.

Signed-off-by: Johannes Schindelin <[email protected]>
This was pull request git-for-windows#1645 from ZCube/master

Support windows container.

Signed-off-by: Johannes Schindelin <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
Handle Ctrl+C in Git Bash nicely

Signed-off-by: Johannes Schindelin <[email protected]>
This branch allows third-party tools to call `git status
--no-lock-index` to avoid lock contention with the interactive Git usage
of the actual human user.

Signed-off-by: Johannes Schindelin <[email protected]>
…ored-directory-gracefully

Phase out `--show-ignored-directory` gracefully
Add a README.md for GitHub goodness.

Signed-off-by: Johannes Schindelin <[email protected]>
@dscho
Copy link
Member Author

dscho commented Nov 4, 2019

As promised, the range-diff:

  1:  dfe4cc634ae =   1:  8cdd7678de0 t9350: point out that refs are not updated correctly
  6:  f3bfb825187 =   2:  bac2140a9ba reset: support the experimental --stdin option
  2:  af1905ff75b =   3:  0c1f02648e8 transport-helper: add trailing --
  3:  e891f1be109 =   4:  6469e8473c5 remote-helper: check helper status after import/export
  4:  9e6b28d17dc <   -:  ----------- Fix launching of externals from Unicode paths
  5:  eb8bf624492 <   -:  ----------- setup_git_directory(): handle UNC paths correctly
 10:  de2c5df569c =   5:  847d6d4a4e9 mingw: demonstrate a problem with certain absolute paths
  7:  07401e05b7d =   6:  aadca100195 Always auto-gc after calling a fast-import transport
  8:  72255809328 <   -:  ----------- Make non-.exe externals work again
  9:  d104dfaf11c <   -:  ----------- Fix .git/ discovery at the root of UNC shares
 11:  7437c0ce35a <   -:  ----------- setup_git_directory(): handle UNC root paths correctly
 12:  ebc141c5531 =   7:  4d9303f7020 diffcore-rename: speed up register_rename_src
 13:  31587ddc6f2 =   8:  beb1b6047ab mingw: allow absolute paths without drive prefix
 15:  04086a64f06 =   9:  ae0d04ce080 archive: replace write_or_die() calls with write_block_or_die()
 20:  3fcd97e211a !  10:  50f32210c75 Start to implement a built-in version of `git add --interactive`
    @@ Documentation/config/add.txt: add.ignore-errors (deprecated)::
     +	instead of the Perl script version. Is `false` by default.
     
      ## Makefile ##
    -@@ Makefile: LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null
    - 	-name '*.h' -print))
    +@@ Makefile: LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat
    + 	-name '*.h' -print)))
      
      LIB_OBJS += abspath.o
     +LIB_OBJS += add-interactive.o
    @@ t/README: GIT_TEST_STASH_USE_BUILTIN=<boolean>, when false, disables the
      git-config(1).
      
     +GIT_TEST_ADD_I_USE_BUILTIN=<boolean>, when true, enables the
    -+builtin version of git add -i. See 'add.interactive.useBuiltin' in
    ++built-in version of git add -i. See 'add.interactive.useBuiltin' in
     +git-config(1).
     +
      GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading
 14:  a7c96637266 =  11:  e5fd203785a t5580: test cloning without file://, test fetching via UNC paths
 16:  1dec213b0b8 <   -:  ----------- mingw: support UNC in git clone file://server/share/repo
 17:  22162b72f0f =  12:  93a179f296f mingw: handle absolute paths in expand_user_path()
 18:  bfd23678e4e =  13:  d05867c4912 mingw: add a helper function to attach GDB to the current process
 19:  ad8be6ab200 =  14:  6ee329f6a9f archive: avoid spawning `gzip`
 21:  4106c550e59 !  15:  9fbca294e3e diff: export diffstat interface
    @@ Commit message
         builtin implementation of git-add--interactive's status.
     
         Signed-off-by: Daniel Ferreira <[email protected]>
    -    Signed-off-by: Slavica Djukic <[email protected]>
    +    Signed-off-by: Slavica ─Éuki─ç <[email protected]>
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## diff.c ##
    @@ diff.c: static void pprint_rename(struct strbuf *name, const char *a, const char
      static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
      					  const char *name_a,
      					  const char *name_b)
    +@@ diff.c: static void show_dirstat_by_line(struct diffstat_t *data, struct diff_options *o
    + 	gather_dirstat(options, &dir, changed, "", 0);
    + }
    + 
    +-static void free_diffstat_info(struct diffstat_t *diffstat)
    ++void free_diffstat_info(struct diffstat_t *diffstat)
    + {
    + 	int i;
    + 	for (i = 0; i < diffstat->nr; i++) {
     @@ diff.c: void diff_flush(struct diff_options *options)
      	    dirstat_by_line) {
      		struct diffstat_t diffstat;
    @@ diff.h: void diff_change(struct diff_options *,
      
     +void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
     +		      struct diff_queue_struct *q);
    ++void free_diffstat_info(struct diffstat_t *diffstat);
     +
      #define DIFF_SETUP_REVERSE      	1
      #define DIFF_SETUP_USE_SIZE_CACHE	4
 22:  457f759265d !  16:  008477c7e10 built-in add -i: implement the `status` command
    @@ Commit message
         built-in add -i: implement the `status` command
     
         This implements the `status` command of `git add -i`. The data
    -    structures introduced in this commit will be extended as needed later.
    +    structures introduced in this commit will be extended later, as needed.
     
         At this point, we re-implement only part of the `list_and_choose()`
         function of the Perl script `git-add--interactive.perl` and call it
    @@ Commit message
         will be used to implement the main loop of the built-in `git add -i`, at
         which point the new `status` command can actually be used.
     
    -    Note that we pass the list of items as a `struct item **` as opposed to
    -    a `struct item *`, to allow for the actual items to contain much more
    -    information than merely the name.
    -
         Signed-off-by: Daniel Ferreira <[email protected]>
    -    Signed-off-by: Slavica Djukic <[email protected]>
    +    Signed-off-by: Slavica ─Éuki─ç <[email protected]>
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    @@ add-interactive.c
     +#include "diffcore.h"
     +#include "revision.h"
     +#include "refs.h"
    ++#include "string-list.h"
     +
    -+struct item {
    -+	const char *name;
    ++struct add_i_state {
    ++	struct repository *r;
     +};
     +
    ++static void init_add_i_state(struct add_i_state *s, struct repository *r)
    ++{
    ++       s->r = r;
    ++}
    ++
     +struct list_options {
     +	const char *header;
    -+	void (*print_item)(int i, struct item *item, void *print_item_data);
    ++	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
     +	void *print_item_data;
     +};
     +
    -+static void list(struct item **list, size_t nr, struct list_options *opts)
    ++static void list(struct string_list *list, struct list_options *opts)
     +{
     +	int i;
     +
    -+	if (!nr)
    ++	if (!list->nr)
     +		return;
     +
     +	if (opts->header)
     +		printf("%s\n", opts->header);
     +
    -+	for (i = 0; i < nr; i++) {
    -+		opts->print_item(i, list[i], opts->print_item_data);
    ++	for (i = 0; i < list->nr; i++) {
    ++		opts->print_item(i, list->items + i, opts->print_item_data);
     +		putchar('\n');
     +	}
     +}
    @@ add-interactive.c
     +	unsigned seen:1, binary:1;
     +};
     +
    -+struct file_list {
    -+	struct file_item {
    -+		struct item item;
    -+		struct adddel index, worktree;
    -+	} **file;
    -+	size_t nr, alloc;
    ++struct file_item {
    ++	struct adddel index, worktree;
     +};
     +
    -+static void add_file_item(struct file_list *list, const char *name)
    -+{
    -+	struct file_item *item;
    -+
    -+	FLEXPTR_ALLOC_STR(item, item.name, name);
    -+
    -+	ALLOC_GROW(list->file, list->nr + 1, list->alloc);
    -+	list->file[list->nr++] = item;
    -+}
    -+
    -+static void reset_file_list(struct file_list *list)
    ++static void add_file_item(struct string_list *files, const char *name)
     +{
    -+	size_t i;
    ++	struct file_item *item = xcalloc(sizeof(*item), 1);
     +
    -+	for (i = 0; i < list->nr; i++)
    -+		free(list->file[i]);
    -+	list->nr = 0;
    -+}
    -+
    -+static void release_file_list(struct file_list *list)
    -+{
    -+	reset_file_list(list);
    -+	FREE_AND_NULL(list->file);
    -+	list->alloc = 0;
    -+}
    -+
    -+static int file_item_cmp(const void *a, const void *b)
    -+{
    -+	const struct file_item * const *f1 = a;
    -+	const struct file_item * const *f2 = b;
    -+
    -+	return strcmp((*f1)->item.name, (*f2)->item.name);
    ++	string_list_append(files, name)->util = item;
     +}
     +
     +struct pathname_entry {
     +	struct hashmap_entry ent;
    -+	size_t index;
    -+	char pathname[FLEX_ARRAY];
    ++	const char *name;
    ++	struct file_item *item;
     +};
     +
     +static int pathname_entry_cmp(const void *unused_cmp_data,
    -+			      const void *entry, const void *entry_or_key,
    -+			      const void *pathname)
    ++			      const struct hashmap_entry *he1,
    ++			      const struct hashmap_entry *he2,
    ++			      const void *name)
     +{
    -+	const struct pathname_entry *e1 = entry, *e2 = entry_or_key;
    ++	const struct pathname_entry *e1 =
    ++		container_of(he1, const struct pathname_entry, ent);
    ++	const struct pathname_entry *e2 =
    ++		container_of(he2, const struct pathname_entry, ent);
     +
    -+	return strcmp(e1->pathname,
    -+		      pathname ? (const char *)pathname : e2->pathname);
    ++	return strcmp(e1->name, name ? (const char *)name : e2->name);
     +}
     +
     +struct collection_status {
    @@ add-interactive.c
     +
     +	const char *reference;
     +
    -+	struct file_list *list;
    ++	struct string_list *files;
     +	struct hashmap file_map;
     +};
     +
    @@ add-interactive.c
     +		const char *name = stat.files[i]->name;
     +		int hash = strhash(name);
     +		struct pathname_entry *entry;
    -+		size_t file_index;
    -+		struct file_item *file;
    ++		struct file_item *file_item;
     +		struct adddel *adddel;
     +
    -+		entry = hashmap_get_from_hash(&s->file_map, hash, name);
    -+		if (entry)
    -+			file_index = entry->index;
    -+		else {
    -+			FLEX_ALLOC_STR(entry, pathname, name);
    -+			hashmap_entry_init(entry, hash);
    -+			entry->index = file_index = s->list->nr;
    -+			hashmap_add(&s->file_map, entry);
    ++		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
    ++						    struct pathname_entry, ent);
    ++		if (!entry) {
    ++			add_file_item(s->files, name);
     +
    -+			add_file_item(s->list, name);
    ++			entry = xcalloc(sizeof(*entry), 1);
    ++			hashmap_entry_init(&entry->ent, hash);
    ++			entry->name = s->files->items[s->files->nr - 1].string;
    ++			entry->item = s->files->items[s->files->nr - 1].util;
    ++			hashmap_add(&s->file_map, &entry->ent);
     +		}
    -+		file = s->list->file[file_index];
     +
    -+		adddel = s->phase == FROM_INDEX ? &file->index : &file->worktree;
    ++		file_item = entry->item;
    ++		adddel = s->phase == FROM_INDEX ?
    ++			&file_item->index : &file_item->worktree;
     +		adddel->seen = 1;
     +		adddel->add = stat.files[i]->added;
     +		adddel->del = stat.files[i]->deleted;
     +		if (stat.files[i]->is_binary)
     +			adddel->binary = 1;
     +	}
    ++	free_diffstat_info(&stat);
     +}
     +
    -+static int get_modified_files(struct repository *r, struct file_list *list,
    ++static int get_modified_files(struct repository *r, struct string_list *files,
     +			      const struct pathspec *ps)
     +{
     +	struct object_id head_oid;
    @@ add-interactive.c
     +					     &head_oid, NULL);
     +	struct collection_status s = { FROM_WORKTREE };
     +
    -+	if (repo_read_index_preload(r, ps, 0) < 0)
    ++	if (discard_index(r->index) < 0 ||
    ++	    repo_read_index_preload(r, ps, 0) < 0)
     +		return error(_("could not read index"));
     +
    -+	s.list = list;
    ++	string_list_clear(files, 1);
    ++	s.files = files;
     +	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
     +
     +	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
    @@ add-interactive.c
     +			run_diff_files(&rev, 0);
     +		}
     +	}
    -+	hashmap_free(&s.file_map, 1);
    ++	hashmap_free_entries(&s.file_map, struct pathname_entry, ent);
     +
     +	/* While the diffs are ordered already, we ran *two* diffs... */
    -+	QSORT(list->file, list->nr, file_item_cmp);
    ++	string_list_sort(files);
     +
     +	return 0;
     +}
     +
    -+static void populate_wi_changes(struct strbuf *buf,
    ++static void render_adddel(struct strbuf *buf,
     +				struct adddel *ad, const char *no_changes)
     +{
     +	if (ad->binary)
    @@ add-interactive.c
     +	struct strbuf buf, index, worktree;
     +};
     +
    -+static void print_file_item(int i, struct item *item,
    ++static void print_file_item(int i, struct string_list_item *item,
     +			    void *print_file_item_data)
     +{
    -+	struct file_item *c = (struct file_item *)item;
    ++	struct file_item *c = item->util;
     +	struct print_file_item_data *d = print_file_item_data;
     +
     +	strbuf_reset(&d->index);
     +	strbuf_reset(&d->worktree);
     +	strbuf_reset(&d->buf);
     +
    -+	populate_wi_changes(&d->worktree, &c->worktree, _("nothing"));
    -+	populate_wi_changes(&d->index, &c->index, _("unchanged"));
    ++	render_adddel(&d->worktree, &c->worktree, _("nothing"));
    ++	render_adddel(&d->index, &c->index, _("unchanged"));
     +	strbuf_addf(&d->buf, d->modified_fmt,
    -+		    d->index.buf, d->worktree.buf, item->name);
    ++		    d->index.buf, d->worktree.buf, item->string);
     +
     +	printf(" %2d: %s", i + 1, d->buf.buf);
     +}
     +
    -+static int run_status(struct repository *r, const struct pathspec *ps,
    -+		      struct file_list *files, struct list_options *opts)
    ++static int run_status(struct add_i_state *s, const struct pathspec *ps,
    ++		      struct string_list *files, struct list_options *opts)
     +{
    -+	reset_file_list(files);
    -+
    -+	if (get_modified_files(r, files, ps) < 0)
    ++	if (get_modified_files(s->r, files, ps) < 0)
     +		return -1;
     +
    -+	if (files->nr)
    -+		list((struct item **)files->file, files->nr, opts);
    ++	list(files, opts);
     +	putchar('\n');
     +
     +	return 0;
    @@ add-interactive.c
      int run_add_i(struct repository *r, const struct pathspec *ps)
      {
     -	die(_("No commands are available in the built-in `git add -i` yet!"));
    ++	struct add_i_state s = { NULL };
     +	struct print_file_item_data print_file_item_data = {
     +		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     +	};
    @@ add-interactive.c
     +		NULL, print_file_item, &print_file_item_data
     +	};
     +	struct strbuf header = STRBUF_INIT;
    -+	struct file_list files = { NULL };
    ++	struct string_list files = STRING_LIST_INIT_DUP;
     +	int res = 0;
     +
    ++	init_add_i_state(&s, r);
     +	strbuf_addstr(&header, "      ");
     +	strbuf_addf(&header, print_file_item_data.modified_fmt,
     +		    _("staged"), _("unstaged"), _("path"));
     +	opts.header = header.buf;
     +
    -+	res = run_status(r, ps, &files, &opts);
    ++	if (discard_index(r->index) < 0 ||
    ++	    repo_read_index(r) < 0 ||
    ++	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
    ++					 NULL, NULL, NULL) < 0)
    ++		warning(_("could not refresh index"));
    ++
    ++	res = run_status(&s, ps, &files, &opts);
     +
    -+	release_file_list(&files);
    ++	string_list_clear(&files, 1);
     +	strbuf_release(&print_file_item_data.buf);
     +	strbuf_release(&print_file_item_data.index);
     +	strbuf_release(&print_file_item_data.worktree);
 23:  a0b99042966 <   -:  ----------- built-in add -i: refresh the index before running `status`
 24:  21c51409f95 <   -:  ----------- built-in add -i: color the header in the `status` command
  -:  ----------- >  17:  cfc303a3e88 built-in add -i: color the header in the `status` command
 25:  5c29435e3d5 !  18:  1e662382825 built-in add -i: implement the main loop
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: struct item {
    - };
    +@@ add-interactive.c: static void init_add_i_state(struct add_i_state *s, struct repository *r)
    + }
      
      struct list_options {
     +	int columns;
      	const char *header;
    - 	void (*print_item)(int i, struct item *item, void *print_item_data);
    + 	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
      	void *print_item_data;
     @@ add-interactive.c: struct list_options {
    - static void list(struct item **list, size_t nr,
    - 		 struct add_i_state *s, struct list_options *opts)
    + static void list(struct add_i_state *s, struct string_list *list,
    + 		 struct list_options *opts)
      {
     -	int i;
     +	int i, last_lf = 0;
      
    - 	if (!nr)
    + 	if (!list->nr)
      		return;
    -@@ add-interactive.c: static void list(struct item **list, size_t nr,
    +@@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *list,
      
    - 	for (i = 0; i < nr; i++) {
    - 		opts->print_item(i, list[i], opts->print_item_data);
    + 	for (i = 0; i < list->nr; i++) {
    + 		opts->print_item(i, list->items + i, opts->print_item_data);
     +
     +		if ((opts->columns) && ((i + 1) % (opts->columns))) {
     +			putchar('\t');
    @@ add-interactive.c: static void list(struct item **list, size_t nr,
     + * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
     + * `LIST_AND_CHOOSE_QUIT` is returned.
     + */
    -+static ssize_t list_and_choose(struct item **items, size_t nr,
    -+			       struct add_i_state *s,
    ++static ssize_t list_and_choose(struct add_i_state *s, struct string_list *items,
     +			       struct list_and_choose_options *opts)
     +{
     +	struct strbuf input = STRBUF_INIT;
    @@ add-interactive.c: static void list(struct item **list, size_t nr,
     +
     +		strbuf_reset(&input);
     +
    -+		list(items, nr, s, &opts->list_opts);
    ++		list(s, items, &opts->list_opts);
     +
     +		printf("%s%s", opts->prompt, "> ");
     +		fflush(stdout);
    @@ add-interactive.c: static void list(struct item **list, size_t nr,
     +			}
     +
     +			p[sep] = '\0';
    -+			if (index < 0 || index >= nr)
    ++			if (index < 0 || index >= items->nr)
     +				printf(_("Huh (%s)?\n"), p);
     +			else {
     +				res = index;
    @@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pat
      	return 0;
      }
      
    -+static void print_command_item(int i, struct item *item,
    ++typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
    ++			 struct string_list *files,
    ++			 struct list_options *opts);
    ++
    ++static void print_command_item(int i, struct string_list_item *item,
     +			       void *print_command_item_data)
     +{
    -+	printf(" %2d: %s", i + 1, item->name);
    ++	printf(" %2d: %s", i + 1, item->string);
     +}
    -+
    -+struct command_item {
    -+	struct item item;
    -+	int (*command)(struct add_i_state *s, const struct pathspec *ps,
    -+		       struct file_list *files, struct list_options *opts);
    -+};
     +
      int run_add_i(struct repository *r, const struct pathspec *ps)
      {
    @@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pat
     +		{ 4, N_("*** Commands ***"), print_command_item, NULL },
     +		N_("What now")
     +	};
    -+	struct command_item
    -+		status = { { "status" }, run_status };
    -+	struct command_item *commands[] = {
    -+		&status
    ++	struct {
    ++		const char *string;
    ++		command_t command;
    ++	} command_list[] = {
    ++		{ "status", run_status },
     +	};
    ++	struct string_list commands = STRING_LIST_INIT_NODUP;
     +
      	struct print_file_item_data print_file_item_data = {
      		"%12s %12s %s", STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
    @@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pat
     +		0, NULL, print_file_item, &print_file_item_data
      	};
      	struct strbuf header = STRBUF_INIT;
    - 	struct file_list files = { NULL };
    + 	struct string_list files = STRING_LIST_INIT_DUP;
     +	ssize_t i;
      	int res = 0;
      
    - 	if (init_add_i_state(r, &s))
    ++	for (i = 0; i < ARRAY_SIZE(command_list); i++)
    ++		string_list_append(&commands, command_list[i].string)
    ++			->util = command_list[i].command;
    ++
    + 	init_add_i_state(&s, r);
    ++
    + 	strbuf_addstr(&header, "      ");
    + 	strbuf_addf(&header, print_file_item_data.modified_fmt,
    + 		    _("staged"), _("unstaged"), _("path"));
     @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 	if (run_status(&s, ps, &files, &opts) < 0)
    - 		res = -1;
    + 
    + 	res = run_status(&s, ps, &files, &opts);
      
     +	for (;;) {
    -+		i = list_and_choose((struct item **)commands,
    -+				    ARRAY_SIZE(commands), &s, &main_loop_opts);
    ++		i = list_and_choose(&s, &commands, &main_loop_opts);
     +		if (i == LIST_AND_CHOOSE_QUIT) {
     +			printf(_("Bye.\n"));
     +			res = 0;
     +			break;
     +		}
    -+		if (i != LIST_AND_CHOOSE_ERROR)
    -+			res = commands[i]->command(&s, ps, &files, &opts);
    ++		if (i != LIST_AND_CHOOSE_ERROR) {
    ++			command_t command = commands.items[i].util;
    ++			res = command(&s, ps, &files, &opts);
    ++		}
     +	}
     +
    - 	release_file_list(&files);
    + 	string_list_clear(&files, 1);
      	strbuf_release(&print_file_item_data.buf);
      	strbuf_release(&print_file_item_data.index);
    + 	strbuf_release(&print_file_item_data.worktree);
    + 	strbuf_release(&header);
    ++	string_list_clear(&commands, 0);
    + 
    + 	return res;
    + }
 26:  235fc8495b3 <   -:  ----------- Add a function to determine unique prefixes for a list of strings
 27:  54039aa229a <   -:  ----------- built-in add -i: show unique prefixes of the commands
  -:  ----------- >  19:  ee39564ee8d built-in add -i: show unique prefixes of the commands
 28:  7c99022bfa4 !  20:  16459c70799 built-in add -i: support `?` (prompt help)
    @@ add-interactive.c: struct add_i_state {
      };
      
      static void init_color(struct repository *r, struct add_i_state *s,
    -@@ add-interactive.c: static int init_add_i_state(struct repository *r, struct add_i_state *s)
    +@@ add-interactive.c: static void init_add_i_state(struct add_i_state *s, struct repository *r)
      	s->use_color = want_color(s->use_color);
      
      	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
     +	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
    - 
    - 	return 0;
      }
    + 
    + /*
     @@ add-interactive.c: struct list_and_choose_options {
      	struct list_options list_opts;
      
    @@ add-interactive.c: struct list_and_choose_options {
      };
      
      #define LIST_AND_CHOOSE_ERROR (-1)
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
      		if (!input.len)
      			break;
      
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      		p = input.buf;
      		for (;;) {
      			size_t sep = strcspn(p, " \t\r\n,");
    -@@ add-interactive.c: struct command_item {
    - 		       struct file_list *files, struct list_options *opts);
    - };
    +@@ add-interactive.c: static void print_command_item(int i, struct string_list_item *item,
    + 		       item->string + util->prefix_length);
    + }
      
     +static void command_prompt_help(struct add_i_state *s)
     +{
    @@ add-interactive.c: struct command_item {
     -		N_("What now")
     +		N_("What now"), command_prompt_help
      	};
    - 	struct command_item
    - 		status = { { "status" }, run_status };
    + 	struct {
    + 		const char *string;
 29:  1160a4fefd5 !  21:  b39de6551ff built-in add -i: use color in the main loop
    @@
      ## Metadata ##
    -Author: Slavica Djukic <[email protected]>
    +Author: Slavica ─Éuki─ç <[email protected]>
     
      ## Commit message ##
         built-in add -i: use color in the main loop
    @@ Commit message
         The error messages as well as the unique prefixes are colored in `git
         add -i` by default; We need to do the same in the built-in version.
     
    -    Signed-off-by: Slavica Djukic <[email protected]>
    +    Signed-off-by: Slavica ─Éuki─ç <[email protected]>
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    @@ add-interactive.c: struct add_i_state {
      };
      
      static void init_color(struct repository *r, struct add_i_state *s,
    -@@ add-interactive.c: static int init_add_i_state(struct repository *r, struct add_i_state *s)
    +@@ add-interactive.c: static void init_add_i_state(struct add_i_state *s, struct repository *r)
      
      	init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD);
      	init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED);
     +	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
     +	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
     +	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
    - 
    - 	return 0;
      }
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
      
    - 		list(items, nr, s, &opts->list_opts);
    + /*
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
    + 
    + 		list(s, &items->items, &opts->list_opts);
      
     -		printf("%s%s", opts->prompt, "> ");
     +		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      		fflush(stdout);
      
      		if (strbuf_getline(&input, stdin) == EOF) {
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    - 				index = find_unique(p, items, nr);
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
    + 				index = find_unique(p, items);
      
    - 			if (index < 0 || index >= nr)
    + 			if (index < 0 || index >= items->items.nr)
     -				printf(_("Huh (%s)?\n"), p);
     +				color_fprintf_ln(stdout, s->error_color,
     +						 _("Huh (%s)?"), p);
      			else {
      				res = index;
      				break;
    -@@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pathspec *ps,
    - 	return 0;
    - }
    +@@ add-interactive.c: struct command_item {
    + 	command_t command;
    + };
      
     +struct print_command_item_data {
     +	const char *color, *reset;
     +};
     +
    - static void print_command_item(int i, struct prefix_item *item,
    + static void print_command_item(int i, struct string_list_item *item,
      			       void *print_command_item_data)
      {
     +	struct print_command_item_data *d = print_command_item_data;
    -+
    - 	if (!item->prefix_length ||
    - 	    !is_valid_prefix(item->name, item->prefix_length))
    - 		printf(" %2d: %s", i + 1, item->name);
    + 	struct command_item *util = item->util;
    + 
    + 	if (!util->prefix_length ||
    + 	    !is_valid_prefix(item->string, util->prefix_length))
    + 		printf(" %2d: %s", i + 1, item->string);
      	else
    --		printf(" %3d: [%.*s]%s", i + 1,
    --		       (int)item->prefix_length, item->name,
    +-		printf(" %2d: [%.*s]%s", i + 1,
    +-		       (int)util->prefix_length, item->string,
    +-		       item->string + util->prefix_length);
     +		printf(" %2d: %s%.*s%s%s", i + 1,
    -+		       d->color, (int)item->prefix_length, item->name, d->reset,
    - 		       item->name + item->prefix_length);
    ++		       d->color, (int)util->prefix_length, item->string,
    ++		       d->reset, item->string + util->prefix_length);
      }
      
    + static void command_prompt_help(struct add_i_state *s)
     @@ add-interactive.c: static void command_prompt_help(struct add_i_state *s)
      int run_add_i(struct repository *r, const struct pathspec *ps)
      {
      	struct add_i_state s = { NULL };
    -+	struct print_command_item_data data;
    ++	struct print_command_item_data data = { "[", "]" };
      	struct list_and_choose_options main_loop_opts = {
     -		{ 4, N_("*** Commands ***"), print_command_item, NULL },
     +		{ 4, N_("*** Commands ***"), print_command_item, &data },
      		N_("What now"), command_prompt_help
      	};
    - 	struct command_item
    + 	struct {
     @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 	if (init_add_i_state(r, &s))
    - 		return error("could not parse `add -i` config");
    + 
    + 	init_add_i_state(&s, r);
      
     +	/*
     +	 * When color was asked for, use the prompt color for
    @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps
     +	if (s.use_color) {
     +		data.color = s.prompt_color;
     +		data.reset = s.reset_color;
    -+	} else {
    -+		data.color = "[";
    -+		data.reset = "]";
     +	}
     +
      	strbuf_addstr(&header, "      ");
 30:  34f43346d7d !  22:  add05aef517 built-in add -i: implement the `help` command
    @@
      ## Metadata ##
    -Author: Johannes Schindelin <[email protected]>
    +Author: Slavica ─Éuki─ç <[email protected]>
     
      ## Commit message ##
         built-in add -i: implement the `help` command
    @@ Commit message
         To make sure that it renders exactly like the Perl version of `git add
         -i`, we also add a test case for that to `t3701-add-interactive.sh`.
     
    -    Signed-off-by: Slavica Djukic <[email protected]>
    +    Signed-off-by: Slavica ─Éuki─ç <[email protected]>
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    @@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pat
      	return 0;
      }
      
    -+static int run_help(struct add_i_state *s, const struct pathspec *ps,
    -+		    struct file_list *files, struct list_options *opts)
    ++static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
    ++		    struct string_list *unused_files,
    ++		    struct list_options *unused_opts)
     +{
    -+	const char *help_color = s->help_color;
    -+
    -+	color_fprintf_ln(stdout, help_color, "status        - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "status        - %s",
     +			 _("show paths with changes"));
    -+	color_fprintf_ln(stdout, help_color, "update        - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "update        - %s",
     +			 _("add working tree state to the staged set of changes"));
    -+	color_fprintf_ln(stdout, help_color, "revert        - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "revert        - %s",
     +			 _("revert staged set of changes back to the HEAD version"));
    -+	color_fprintf_ln(stdout, help_color, "patch         - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "patch         - %s",
     +			 _("pick hunks and update selectively"));
    -+	color_fprintf_ln(stdout, help_color, "diff          - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "diff          - %s",
     +			 _("view diff between HEAD and index"));
    -+	color_fprintf_ln(stdout, help_color, "add untracked - %s",
    ++	color_fprintf_ln(stdout, s->help_color, "add untracked - %s",
     +			 _("add contents of untracked files to the staged set of changes"));
     +
     +	return 0;
     +}
     +
    - struct print_command_item_data {
    - 	const char *color, *reset;
    - };
    + typedef int (*command_t)(struct add_i_state *s, const struct pathspec *ps,
    + 			 struct string_list *files,
    + 			 struct list_options *opts);
     @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 		N_("What now"), command_prompt_help
    - 	};
    - 	struct command_item
    --		status = { { "status" }, run_status };
    -+		status = { { "status" }, run_status },
    -+		help = { { "help" }, run_help };
    - 	struct command_item *commands[] = {
    --		&status
    -+		&status,
    -+		&help
    + 		command_t command;
    + 	} command_list[] = {
    + 		{ "status", run_status },
    ++		{ "help", run_help },
      	};
    + 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
      
    - 	struct print_file_item_data print_file_item_data = {
     
      ## t/t3701-add-interactive.sh ##
     @@ t/t3701-add-interactive.sh: test_expect_success 'checkout -p works with pathological context lines' '
 31:  f3b3f0ed911 !  23:  c5b960a0ead built-in add -i: allow filtering the modified files list
    @@ Metadata
      ## Commit message ##
         built-in add -i: allow filtering the modified files list
     
    -    In `update` command of `git add -i`, we are primarily interested in the
    +    In the `update` command of `git add -i`, we are primarily interested in the
         list of modified files that have worktree (i.e. unstaged) changes.
     
    +    At the same time, we need to determine _also_ the staged changes, to be
    +    able to produce the full added/deleted information.
    +
         The Perl script version of `git add -i` has a parameter of the
         `list_modified()` function for that matter. In C, we can be a lot more
         precise, using an `enum`.
     
         The C implementation of the filter also has an easier time to avoid
         unnecessary work, simply by using an adaptive order of the `diff-index`
    -    and `diff-files` calls, and then not adding unnecessary entries in the
    -    first place.
    +    and `diff-files` phases, and then skipping files in the second phase
    +    when they have not been seen in the first phase.
     
         Signed-off-by: Johannes Schindelin <[email protected]>
     
    @@ add-interactive.c: struct collection_status {
      	const char *reference;
      
     +	unsigned skip_unseen:1;
    - 	struct file_list *list;
    + 	struct string_list *files;
      	struct hashmap file_map;
      };
     @@ add-interactive.c: static void collect_changes_cb(struct diff_queue_struct *q,
    - 		entry = hashmap_get_from_hash(&s->file_map, hash, name);
    - 		if (entry)
    - 			file_index = entry->index;
    -+		else if (s->skip_unseen)
    -+			continue;
    - 		else {
    - 			FLEX_ALLOC_STR(entry, pathname, name);
    - 			hashmap_entry_init(entry, hash);
    + 		entry = hashmap_get_entry_from_hash(&s->file_map, hash, name,
    + 						    struct pathname_entry, ent);
    + 		if (!entry) {
    ++			if (s->skip_unseen)
    ++				continue;
    ++
    + 			add_file_item(s->files, name);
    + 
    + 			entry = xcalloc(sizeof(*entry), 1);
     @@ add-interactive.c: static void collect_changes_cb(struct diff_queue_struct *q,
    - 	}
    + 	free_diffstat_info(&stat);
      }
      
    --static int get_modified_files(struct repository *r, struct file_list *list,
    +-static int get_modified_files(struct repository *r, struct string_list *files,
     +enum modified_files_filter {
     +	NO_FILTER = 0,
     +	WORKTREE_ONLY = 1,
    @@ add-interactive.c: static void collect_changes_cb(struct diff_queue_struct *q,
     +
     +static int get_modified_files(struct repository *r,
     +			      enum modified_files_filter filter,
    -+			      struct file_list *list,
    ++			      struct string_list *files,
      			      const struct pathspec *ps)
      {
      	struct object_id head_oid;
    @@ add-interactive.c: static void collect_changes_cb(struct diff_queue_struct *q,
      	struct collection_status s = { FROM_WORKTREE };
     +	int i;
      
    - 	if (repo_read_index_preload(r, ps, 0) < 0)
    - 		return error(_("could not read index"));
    -@@ add-interactive.c: static int get_modified_files(struct repository *r, struct file_list *list,
    - 	s.list = list;
    + 	if (discard_index(r->index) < 0 ||
    + 	    repo_read_index_preload(r, ps, 0) < 0)
    +@@ add-interactive.c: static int get_modified_files(struct repository *r, struct string_list *files,
    + 	s.files = files;
      	hashmap_init(&s.file_map, pathname_entry_cmp, NULL, 0);
      
     -	for (s.phase = FROM_WORKTREE; s.phase <= FROM_INDEX; s.phase++) {
    @@ add-interactive.c: static int get_modified_files(struct repository *r, struct fi
      		opt.def = is_initial ?
      			empty_tree_oid_hex() : oid_to_hex(&head_oid);
      
    -@@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pathspec *ps,
    +@@ add-interactive.c: static void print_file_item(int i, struct string_list_item *item,
    + static int run_status(struct add_i_state *s, const struct pathspec *ps,
    + 		      struct string_list *files, struct list_options *opts)
      {
    - 	reset_file_list(files);
    - 
     -	if (get_modified_files(s->r, files, ps) < 0)
    -+	if (get_modified_files(s->r, 0, files, ps) < 0)
    ++	if (get_modified_files(s->r, NO_FILTER, files, ps) < 0)
      		return -1;
      
    - 	if (files->nr)
    + 	list(s, files, opts);
 32:  bd5156e9140 !  24:  e8f70db6ad9 built-in add -i: prepare for multi-selection commands
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: static ssize_t find_unique(const char *string,
    +@@ add-interactive.c: static void init_add_i_state(struct add_i_state *s, struct repository *r)
    + struct prefix_item_list {
    + 	struct string_list items;
    + 	struct string_list sorted;
    ++	int *selected; /* for multi-selections */
    + 	size_t min_length, max_length;
    + };
    + #define PREFIX_ITEM_LIST_INIT \
    +-	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, 1, 4 }
    ++	{ STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 }
    + 
    + static void prefix_item_list_clear(struct prefix_item_list *list)
    + {
    + 	string_list_clear(&list->items, 1);
    + 	string_list_clear(&list->sorted, 0);
    ++	FREE_AND_NULL(list->selected);
    + }
    + 
    + static void extend_prefix_length(struct string_list_item *p,
    +@@ add-interactive.c: static ssize_t find_unique(const char *string, struct prefix_item_list *list)
      struct list_options {
      	int columns;
      	const char *header;
    --	void (*print_item)(int i, struct prefix_item *item,
    -+	void (*print_item)(int i, int selected, struct prefix_item *item,
    - 			   void *print_item_data);
    +-	void (*print_item)(int i, struct string_list_item *item, void *print_item_data);
    ++	void (*print_item)(int i, int selected, struct string_list_item *item,
    ++			   void *print_item_data);
      	void *print_item_data;
      };
      
    --static void list(struct prefix_item **list, size_t nr,
    -+static void list(struct prefix_item **list, int *selected, size_t nr,
    - 		 struct add_i_state *s, struct list_options *opts)
    +-static void list(struct add_i_state *s, struct string_list *list,
    ++static void list(struct add_i_state *s, struct string_list *list, int *selected,
    + 		 struct list_options *opts)
      {
      	int i, last_lf = 0;
    -@@ add-interactive.c: static void list(struct prefix_item **list, size_t nr,
    +@@ add-interactive.c: static void list(struct add_i_state *s, struct string_list *list,
      				 "%s", opts->header);
      
    - 	for (i = 0; i < nr; i++) {
    --		opts->print_item(i, list[i], opts->print_item_data);
    -+		opts->print_item(i, selected ? selected[i] : 0, list[i],
    + 	for (i = 0; i < list->nr; i++) {
    +-		opts->print_item(i, list->items + i, opts->print_item_data);
    ++		opts->print_item(i, selected ? selected[i] : 0, list->items + i,
     +				 opts->print_item_data);
      
      		if ((opts->columns) && ((i + 1) % (opts->columns))) {
    @@ add-interactive.c: struct list_and_choose_options {
       *
       * If an error occurred, returns `LIST_AND_CHOOSE_ERROR`. Upon EOF,
       * `LIST_AND_CHOOSE_QUIT` is returned.
    -  */
    --static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    --			       struct add_i_state *s,
    -+static ssize_t list_and_choose(struct prefix_item **items, int *selected,
    -+			       size_t nr, struct add_i_state *s,
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
    + 			       struct prefix_item_list *items,
      			       struct list_and_choose_options *opts)
      {
     +	int singleton = opts->flags & SINGLETON;
    @@ add-interactive.c: struct list_and_choose_options {
     -	ssize_t res = LIST_AND_CHOOSE_ERROR;
     +	ssize_t res = singleton ? LIST_AND_CHOOSE_ERROR : 0;
     +
    -+	if (!selected && !singleton)
    -+		BUG("need a selected array in non-singleton mode");
    ++	if (!singleton) {
    ++		free(items->selected);
    ++		CALLOC_ARRAY(items->selected, items->items.nr);
    ++	}
     +
     +	if (singleton && !immediate)
     +		BUG("singleton requires immediate");
      
    - 	find_unique_prefixes(items, nr, 1, 4);
    + 	find_unique_prefixes(items);
      
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
      
      		strbuf_reset(&input);
      
    --		list(items, nr, s, &opts->list_opts);
    -+		list(items, selected, nr, s, &opts->list_opts);
    +-		list(s, &items->items, &opts->list_opts);
    ++		list(s, &items->items, items->selected, &opts->list_opts);
      
      		color_fprintf(stdout, s->prompt_color, "%s", opts->prompt);
     -		fputs("> ", stdout);
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      			break;
      		}
      		strbuf_trim(&input);
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
      		p = input.buf;
      		for (;;) {
      			size_t sep = strcspn(p, " \t\r\n,");
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      
      			if (!sep) {
      				if (!*p)
    -@@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, size_t nr,
    +@@ add-interactive.c: static ssize_t list_and_choose(struct add_i_state *s,
      				continue;
      			}
      
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
     -				index = strtoul(p, &endp, 10) - 1;
     -				if (endp != p + sep)
     -					index = -1;
    -+			/* Input that begins with '-'; unchoose */
    ++			/* Input that begins with '-'; de-select */
     +			if (*p == '-') {
     +				choose = 0;
     +				p++;
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
     +
     +			if (sep == 1 && *p == '*') {
     +				from = 0;
    -+				to = nr;
    ++				to = items->items.nr;
     +			} else if (isdigit(*p)) {
     +				/* A range can be specified like 5-7 or 5-. */
     +				from = strtoul(p, &endp, 10) - 1;
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      
      			p[sep] = '\0';
     -			if (index < 0)
    --				index = find_unique(p, items, nr);
    +-				index = find_unique(p, items);
     +			if (from < 0) {
    -+				from = find_unique(p, items, nr);
    ++				from = find_unique(p, items);
     +				if (from >= 0)
     +					to = from + 1;
     +			}
      
    --			if (index < 0 || index >= nr)
    -+			if (from < 0 || from >= nr ||
    +-			if (index < 0 || index >= items->items.nr)
    ++			if (from < 0 || from >= items->items.nr ||
     +			    (singleton && from + 1 != to)) {
      				color_fprintf_ln(stdout, s->error_color,
      						 _("Huh (%s)?"), p);
    @@ add-interactive.c: static ssize_t list_and_choose(struct prefix_item **items, si
      				break;
      			}
      
    -+			if (to > nr)
    -+				to = nr;
    ++			if (to > items->items.nr)
    ++				to = items->items.nr;
     +
     +			for (; from < to; from++)
    -+				if (selected[from] != choose) {
    -+					selected[from] = choose;
    ++				if (items->selected[from] != choose) {
    ++					items->selected[from] = choose;
     +					res += choose ? +1 : -1;
     +				}
     +
    @@ add-interactive.c: struct print_file_item_data {
      	struct strbuf buf, index, worktree;
      };
      
    --static void print_file_item(int i, struct prefix_item *item,
    -+static void print_file_item(int i, int selected, struct prefix_item *item,
    +-static void print_file_item(int i, struct string_list_item *item,
    ++static void print_file_item(int i, int selected, struct string_list_item *item,
      			    void *print_file_item_data)
      {
    - 	struct file_item *c = (struct file_item *)item;
    -@@ add-interactive.c: static void print_file_item(int i, struct prefix_item *item,
    + 	struct file_item *c = item->util;
    +@@ add-interactive.c: static void print_file_item(int i, struct string_list_item *item,
      	strbuf_addf(&d->buf, d->modified_fmt,
    - 		    d->index.buf, d->worktree.buf, item->name);
    + 		    d->index.buf, d->worktree.buf, item->string);
      
     -	printf(" %2d: %s", i + 1, d->buf.buf);
     +	printf("%c%2d: %s", selected ? '*' : ' ', i + 1, d->buf.buf);
    @@ add-interactive.c: static void print_file_item(int i, struct prefix_item *item,
      
      static int run_status(struct add_i_state *s, const struct pathspec *ps,
     @@ add-interactive.c: static int run_status(struct add_i_state *s, const struct pathspec *ps,
    + 	if (get_modified_files(s->r, NO_FILTER, files, ps) < 0)
      		return -1;
      
    - 	if (files->nr)
    --		list((struct prefix_item **)files->file, files->nr, s, opts);
    -+		list((struct prefix_item **)files->file, NULL, files->nr,
    -+		     s, opts);
    +-	list(s, files, opts);
    ++	list(s, files, NULL, opts);
      	putchar('\n');
      
      	return 0;
    @@ add-interactive.c: struct print_command_item_data {
      	const char *color, *reset;
      };
      
    --static void print_command_item(int i, struct prefix_item *item,
    -+static void print_command_item(int i, int selected, struct prefix_item *item,
    +-static void print_command_item(int i, struct string_list_item *item,
    ++static void print_command_item(int i, int selected,
    ++			       struct string_list_item *item,
      			       void *print_command_item_data)
      {
      	struct print_command_item_data *d = print_command_item_data;
     @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 	struct print_command_item_data data;
    + 	struct print_command_item_data data = { "[", "]" };
      	struct list_and_choose_options main_loop_opts = {
      		{ 4, N_("*** Commands ***"), print_command_item, &data },
     -		N_("What now"), command_prompt_help
     +		N_("What now"), SINGLETON | IMMEDIATE, command_prompt_help
      	};
    - 	struct command_item
    - 		status = { { "status" }, run_status },
    -@@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 		res = -1;
    - 
    - 	for (;;) {
    --		i = list_and_choose((struct prefix_item **)commands,
    -+		i = list_and_choose((struct prefix_item **)commands, NULL,
    - 				    ARRAY_SIZE(commands), &s, &main_loop_opts);
    - 		if (i == LIST_AND_CHOOSE_QUIT) {
    - 			printf(_("Bye.\n"));
    + 	struct {
    + 		const char *string;
 33:  f4602ba5bf8 <   -:  ----------- built-in add -i: implement the `update` command
  -:  ----------- >  25:  f7b3b9bcaaf built-in add -i: implement the `update` command
 34:  d3c4b7b7240 !  26:  33ebf58b808 built-in add -i: re-implement `revert` in C
    @@ add-interactive.c: static int run_update(struct add_i_state *s, const struct pat
     +}
     +
     +static int run_revert(struct add_i_state *s, const struct pathspec *ps,
    -+		      struct file_list *files,
    ++		      struct prefix_item_list *files,
     +		      struct list_and_choose_options *opts)
     +{
    -+	int res = 0, fd, *selected = NULL;
    ++	int res = 0, fd;
     +	size_t count, i, j;
     +
     +	struct object_id oid;
    @@ add-interactive.c: static int run_update(struct add_i_state *s, const struct pat
     +	struct tree *tree;
     +	struct diff_options diffopt = { NULL };
     +
    -+	reset_file_list(files);
     +	if (get_modified_files(s->r, INDEX_ONLY, files, ps) < 0)
     +		return -1;
     +
    -+	if (!files->nr) {
    ++	if (!files->items.nr) {
     +		putchar('\n');
     +		return 0;
     +	}
     +
     +	opts->prompt = N_("Revert");
    -+	CALLOC_ARRAY(selected, files->nr);
    -+
    -+	count = list_and_choose((struct prefix_item **)files->file,
    -+				selected, files->nr, s, opts);
    ++	count = list_and_choose(s, files, opts);
     +	if (count <= 0)
     +		goto finish_revert;
     +
    @@ add-interactive.c: static int run_update(struct add_i_state *s, const struct pat
     +	}
     +
     +	ALLOC_ARRAY(paths, count + 1);
    -+	for (i = j = 0; i < files->nr; i++)
    -+		if (selected[i])
    -+			paths[j++] = files->file[i]->item.name;
    ++	for (i = j = 0; i < files->items.nr; i++)
    ++		if (files->selected[i])
    ++			paths[j++] = files->items.items[i].string;
     +	paths[j] = NULL;
     +
     +	parse_pathspec(&diffopt.pathspec, 0,
    @@ add-interactive.c: static int run_update(struct add_i_state *s, const struct pat
     +				       COMMIT_LOCK) < 0)
     +		res = -1;
     +	else
    -+		res = repo_refresh_and_write_index(s->r, REFRESH_QUIET, 1);
    ++		res = repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0, 1,
    ++						   NULL, NULL, NULL);
    ++
     +	if (!res)
     +		printf(Q_("reverted %d path\n",
     +			  "reverted %d paths\n", count), (int)count);
     +
     +finish_revert:
     +	putchar('\n');
    -+	free(selected);
     +	return res;
     +}
     +
    - static int run_help(struct add_i_state *s, const struct pathspec *ps,
    - 		    struct file_list *files,
    + static int run_help(struct add_i_state *s, const struct pathspec *unused_ps,
    + 		    struct prefix_item_list *unused_files,
      		    struct list_and_choose_options *opts)
     @@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    - 	struct command_item
    - 		status = { { "status" }, run_status },
    - 		update = { { "update" }, run_update },
    -+		revert = { { "revert" }, run_revert },
    - 		help = { { "help" }, run_help };
    - 	struct command_item *commands[] = {
    --		&status, &update,
    -+		&status, &update, &revert,
    - 		&help
    + 	} command_list[] = {
    + 		{ "status", run_status },
    + 		{ "update", run_update },
    ++		{ "revert", run_revert },
    + 		{ "help", run_help },
      	};
    - 
    + 	struct prefix_item_list commands = PREFIX_ITEM_LIST_INIT;
 35:  c5fac3487ad <   -:  ----------- built-in add -i: re-implement `add-untracked` in C
 36:  f22f7b783cf <   -:  ----------- built-in add -i: implement the `patch` command
 37:  040d8e77105 <   -:  ----------- built-in add -i: re-implement the `diff` command
 38:  0efdd4fca2e <   -:  ----------- built-in add -i: offer the `quit` command
  -:  ----------- >  27:  47cd5b58fcf built-in add -i: re-implement `add-untracked` in C
  -:  ----------- >  28:  6f8d73f0c6b built-in add -i: implement the `patch` command
  -:  ----------- >  29:  9bddac0d57d built-in add -i: re-implement the `diff` command
  -:  ----------- >  30:  6ba19986d74 built-in add -i: offer the `quit` command
 39:  6c1ac7ef245 =  31:  d3f22f56beb t3701: add a test for advanced split-hunk editing
 40:  3ceab76107c =  32:  3b206296589 t3701: avoid depending on the TTY prerequisite
 41:  f417cc7abd7 !  33:  522bb4270fe t3701: add a test for the different `add -p` prompts
    @@ t/t3701-add-interactive.sh: test_expect_success FILEMODE 'stage mode and hunk' '
     +	rm deleted &&
     +	test_write_lines n n n |
     +	git -c core.filemode=true add -p >actual &&
    -+	sed -n "s/^\(Stage .*?\).*/\1/p" actual >actual.filtered &&
    ++	sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered &&
     +	cat >expect <<-\EOF &&
    -+	Stage deletion [y,n,q,a,d,?]?
    -+	Stage mode change [y,n,q,a,d,j,J,g,/,?]?
    -+	Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
    ++	(1/1) Stage deletion [y,n,q,a,d,?]?
    ++	(1/2) Stage mode change [y,n,q,a,d,j,J,g,/,?]?
    ++	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]?
     +	EOF
     +	test_cmp expect actual.filtered
     +'
 42:  37aec53d1f9 =  34:  d2dce89c0c1 t3701: verify the shown messages when nothing can be added
 43:  0a372a71eb3 =  35:  7fea65fd460 t3701: verify that the diff.algorithm config setting is handled
 44:  55e41f87488 =  36:  0f512c107a8 git add -p: use non-zero exit code when the diff generation failed
 45:  6198d09d4fe =  37:  5a158606bd4 apply --allow-overlap: fix a corner case
 46:  620df4a511f !  38:  f5531786a10 built-in add -i: start implementing the `patch` functionality in C
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## Makefile ##
    -@@ Makefile: LIB_H := $(sort $(shell git ls-files '*.h' ':!t/' ':!Documentation/' 2>/dev/null
    +@@ Makefile: LIB_H := $(sort $(patsubst ./%,%,$(shell git ls-files '*.h' ':!t/' ':!Documentat
      
      LIB_OBJS += abspath.o
      LIB_OBJS += add-interactive.o
    @@ add-patch.c (new)
     +			strbuf_addstr(&s->buf, ",j");
     +		if (hunk_index + 1 < s->hunk_nr)
     +			strbuf_addstr(&s->buf, ",J");
    ++		printf("(%"PRIuMAX"/%"PRIuMAX") ",
    ++		       (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr);
     +		printf(_("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf);
     +		fflush(stdout);
     +		if (strbuf_getline(&s->answer, stdin) == EOF)
    @@ add-patch.c (new)
     +		strbuf_reset(&s->buf);
     +		reassemble_patch(s, &s->buf);
     +
    ++		discard_index(s->r->index);
     +		setup_child_process(&cp, s, "apply", "--cached", NULL);
     +		if (pipe_command(&cp, s->buf.buf, s->buf.len,
     +				 NULL, 0, NULL, 0))
     +			error(_("'git apply --cached' failed"));
    -+		repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0);
    ++		if (!repo_read_index(s->r))
    ++			repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0,
    ++						     1, NULL, NULL, NULL);
     +	}
     +
     +	putchar('\n');
    @@ add-patch.c (new)
     +{
     +	struct add_p_state s = { r, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT };
     +
    -+	if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
    ++	if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
    ++	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
    ++					 NULL, NULL, NULL) < 0 ||
     +	    parse_diff(&s, ps) < 0) {
     +		strbuf_release(&s.plain);
     +		return -1;
 47:  b650cb9f678 <   -:  ----------- built-in add -i: wire up the new C code for the `patch` command
  -:  ----------- >  39:  f028c857ec7 built-in add -i: wire up the new C code for the `patch` command
 48:  ada3381d5ee !  40:  70e601b375e built-in add -p: show colored hunks by default
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
      
      		strbuf_reset(&s->buf);
     @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
    - 	if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
    + 					 NULL, NULL, NULL) < 0 ||
      	    parse_diff(&s, ps) < 0) {
      		strbuf_release(&s.plain);
     +		strbuf_release(&s.colored);
 49:  5597cae2649 !  41:  a7e0aa80abd built-in add -p: adjust hunk headers as needed
    @@ Commit message
     
      ## add-interactive.c ##
     @@
    - #include "argv-array.h"
    + #include "dir.h"
      #include "run-command.h"
      
     -struct add_i_state {
    @@ add-interactive.c: static void init_color(struct repository *r, struct add_i_sta
      	free(key);
      }
      
    --static int init_add_i_state(struct repository *r, struct add_i_state *s)
    -+int init_add_i_state(struct repository *r, struct add_i_state *s)
    +-static void init_add_i_state(struct add_i_state *s, struct repository *r)
    ++void init_add_i_state(struct add_i_state *s, struct repository *r)
      {
      	const char *value;
      
    -@@ add-interactive.c: static int init_add_i_state(struct repository *r, struct add_i_state *s)
    +@@ add-interactive.c: static void init_add_i_state(struct add_i_state *s, struct repository *r)
    + 	init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE);
      	init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED);
      	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
    - 
    -+	strlcpy(s->fraginfo_color,
    -+		diff_get_color(s->use_color, DIFF_FRAGINFO), COLOR_MAXLEN);
    -+
    - 	return 0;
    ++	init_color(r, s, "fraginfo", s->fraginfo_color,
    ++		   diff_get_color(s->use_color, DIFF_FRAGINFO));
      }
      
    + /*
     
      ## add-interactive.h ##
     @@
    @@ add-interactive.h
     +	char fraginfo_color[COLOR_MAXLEN];
     +};
     +
    -+int init_add_i_state(struct repository *r, struct add_i_state *s);
    ++void init_add_i_state(struct add_i_state *s, struct repository *r);
     +
      struct repository;
      struct pathspec;
    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
     +				- header->colored_extra_start;
     +		}
     +
    -+		if (s->mode->is_reverse)
    -+			old_offset -= delta;
    -+		else
    -+			new_offset += delta;
    ++		new_offset += delta;
     +
     +		strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
     +			    old_offset, header->old_count,
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
      
      		strbuf_reset(&s->buf);
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
    + 		strbuf_reset(&s->buf);
    + 		reassemble_patch(s, &s->buf);
    + 
    +-		discard_index(s->r->index);
    ++		discard_index(s->s.r->index);
    + 		setup_child_process(&cp, s, "apply", "--cached", NULL);
      		if (pipe_command(&cp, s->buf.buf, s->buf.len,
      				 NULL, 0, NULL, 0))
      			error(_("'git apply --cached' failed"));
    --		repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0);
    -+		repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0);
    +-		if (!repo_read_index(s->r))
    +-			repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0,
    ++		if (!repo_read_index(s->s.r))
    ++			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
    + 						     1, NULL, NULL, NULL);
      	}
      
    - 	putchar('\n');
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
      
      int run_add_p(struct repository *r, const struct pathspec *ps)
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
     +		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     +	};
     +
    -+	if (init_add_i_state(r, &s.s))
    -+		return error("Could not read `add -i` config");
    ++	init_add_i_state(&s.s, r);
      
    - 	if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
    - 	    parse_diff(&s, ps) < 0) {
    + 	if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
    + 	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
 50:  11baa29113a !  42:  fb22ad91a62 built-in add -p: color the prompt and the help text
    @@ Commit message
      ## add-interactive.h ##
     @@ add-interactive.h: struct add_i_state {
      
    - int init_add_i_state(struct repository *r, struct add_i_state *s);
    + void init_add_i_state(struct add_i_state *s, struct repository *r);
      
     +enum color_add_i {
     +	COLOR_HEADER = 0,
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
      			strbuf_addstr(&s->buf, ",j");
      		if (hunk_index + 1 < s->hunk_nr)
      			strbuf_addstr(&s->buf, ",J");
    +-		printf("(%"PRIuMAX"/%"PRIuMAX") ",
    +-		       (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr);
     -		printf(_("Stage this hunk [y,n,a,d%s,?]? "), s->buf.buf);
     +		color_fprintf(stdout, s->s.prompt_color,
    ++			      "(%"PRIuMAX"/%"PRIuMAX") ",
    ++			      (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr);
    ++		color_fprintf(stdout, s->s.prompt_color,
     +			      _("Stage this hunk [y,n,a,d%s,?]? "),
     +			      s->buf.buf);
      		fflush(stdout);
 51:  eb05034b6ba =  43:  efae448aad3 built-in add -p: offer a helpful error message when hunk navigation failed
 52:  1c31b22a848 !  44:  7647db90299 built-in add -p: support multi-file diffs
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
     -		if (hunk_index + 1 < s->hunk_nr)
     +		if (hunk_index + 1 < file_diff->hunk_nr)
      			strbuf_addstr(&s->buf, ",J");
    + 		color_fprintf(stdout, s->s.prompt_color,
    + 			      "(%"PRIuMAX"/%"PRIuMAX") ",
    +-			      (uintmax_t)hunk_index + 1, (uintmax_t)s->hunk_nr);
    ++			      (uintmax_t)hunk_index + 1,
    ++			      (uintmax_t)file_diff->hunk_nr);
      		color_fprintf(stdout, s->s.prompt_color,
      			      _("Stage this hunk [y,n,a,d%s,?]? "),
    + 			      s->buf.buf);
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
      		if (ch == 'y') {
      			hunk->use = USE_HUNK;
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s)
     -		reassemble_patch(s, &s->buf);
     +		reassemble_patch(s, file_diff, &s->buf);
      
    + 		discard_index(s->s.r->index);
      		setup_child_process(&cp, s, "apply", "--cached", NULL);
    - 		if (pipe_command(&cp, s->buf.buf, s->buf.len,
     @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
      	struct add_p_state s = {
      		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
      	};
     +	size_t i;
      
    - 	if (init_add_i_state(r, &s.s))
    - 		return error("Could not read `add -i` config");
    + 	init_add_i_state(&s.s, r);
    + 
     @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
      		return -1;
      	}
 53:  64c9c3d4f57 =  45:  0f827c4563c built-in add -p: handle deleted empty files
 54:  ad1e1fc8647 =  46:  cc101c047b9 built-in app -p: allow selecting a mode change as a "hunk"
 55:  98e70bddc71 !  47:  a0e4df514f0 built-in add -p: show different prompts for mode changes and deletions
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
     +			prompt_mode_type = PROMPT_HUNK;
     +
      		color_fprintf(stdout, s->s.prompt_color,
    + 			      "(%"PRIuMAX"/%"PRIuMAX") ",
    + 			      (uintmax_t)hunk_index + 1,
    + 			      (uintmax_t)file_diff->hunk_nr);
    + 		color_fprintf(stdout, s->s.prompt_color,
     -			      _("Stage this hunk [y,n,a,d%s,?]? "),
     -			      s->buf.buf);
     +			      _(prompt_mode[prompt_mode_type]), s->buf.buf);
 56:  85f0488b311 !  48:  b336dc342d5 built-in add -p: implement the hunk splitting feature
    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
      			    (int)(eol - (plain->buf + file_diff->head.start)),
      			    plain->buf + file_diff->head.start);
      
    -+		if ((marker == '-' || marker == '+') &&
    -+		    (*p == ' ' || *p == '\\'))
    ++		if ((marker == '-' || marker == '+') && *p == ' ')
     +			hunk->splittable_into++;
    -+		if (marker)
    ++		if (marker && *p != '\\')
     +			marker = *p;
     +
      		p = eol == pend ? pend : eol + 1;
    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
      
     +static size_t find_next_line(struct strbuf *sb, size_t offset)
     +{
    -+	char *eol = memchr(sb->buf + offset, '\n', sb->len - offset);
    ++	char *eol;
     +
    ++	if (offset >= sb->len)
    ++		BUG("looking for next line beyond buffer (%d >= %d)\n%s",
    ++		    (int)offset, (int)sb->len, sb->buf);
    ++
    ++	eol = memchr(sb->buf + offset, '\n', sb->len - offset);
     +	if (!eol)
     +		return sb->len;
     +	return eol - sb->buf + 1;
    @@ add-patch.c: static void reassemble_patch(struct add_p_state *s,
     +
     +	while (splittable_into > 1) {
     +		ch = s->plain.buf[current];
    ++
    ++		if (!ch)
    ++			BUG("buffer overrun while splitting hunks");
    ++
    ++		/*
    ++		 * Is this the first context line after a chain of +/- lines?
    ++		 * Then record the start of the next split hunk.
    ++		 */
     +		if ((marker == '-' || marker == '+') && ch == ' ') {
     +			first = 0;
     +			hunk[1].start = current;
    @@ add-patch.c: static void reassemble_patch(struct add_p_state *s,
     +			context_line_count = 0;
     +		}
     +
    ++		/*
    ++		 * Was the previous line a +/- one? Alternatively, is this the
    ++		 * first line (and not a +/- one)?
    ++		 *
    ++		 * Then just increment the appropriate counter and continue
    ++		 * with the next line.
    ++		 *
    ++		 * Otherwise this is the first of a chain of +/- lines.
    ++		 * neither the first of a chain of context lines?
    ++		 */
     +		if (marker != ' ' || (ch != '-' && ch != '+')) {
     +next_hunk_line:
    ++			/* Comment lines are attached to the previous line */
    ++			if (ch == '\\')
    ++				ch = marker ? marker : ' ';
    ++
     +			/* current hunk not done yet */
     +			if (ch == ' ')
     +				context_line_count++;
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
      		} else
      			color_fprintf(stdout, s->s.help_color,
      				      _(help_patch_text));
    +
    + ## t/t3701-add-interactive.sh ##
    +@@ t/t3701-add-interactive.sh: test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
    + 	! grep "^+31" actual
    + '
    + 
    ++test_expect_success 'split hunk with incomplete line at end' '
    ++	git reset --hard &&
    ++	printf "missing LF" >>test &&
    ++	git add test &&
    ++	test_write_lines before 10 20 30 40 50 60 70 >test &&
    ++	git grep --cached missing &&
    ++	test_write_lines s n y q | git add -p &&
    ++	test_must_fail git grep --cached missing &&
    ++	git grep before &&
    ++	test_must_fail git grep --cached before
    ++'
    ++
    + test_expect_failure 'edit, adding lines to the first hunk' '
    + 	test_write_lines 10 11 20 30 40 50 51 60 >test &&
    + 	git reset &&
 57:  56e6923cf02 =  49:  45499e91b3d built-in add -p: coalesce hunks after splitting them
 58:  61a852b5c25 !  50:  991ff42d62a built-in add -p: implement hunk editing
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: int init_add_i_state(struct repository *r, struct add_i_state *s)
    - 
    - 	strlcpy(s->fraginfo_color,
    - 		diff_get_color(s->use_color, DIFF_FRAGINFO), COLOR_MAXLEN);
    -+	strlcpy(s->context_color,
    -+		diff_get_color(s->use_color, DIFF_CONTEXT), COLOR_MAXLEN);
    -+	strlcpy(s->file_old_color,
    -+		diff_get_color(s->use_color, DIFF_FILE_OLD), COLOR_MAXLEN);
    -+	strlcpy(s->file_new_color,
    -+		diff_get_color(s->use_color, DIFF_FILE_NEW), COLOR_MAXLEN);
    -+
    - 
    - 	return 0;
    +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
    + 	init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET);
    + 	init_color(r, s, "fraginfo", s->fraginfo_color,
    + 		   diff_get_color(s->use_color, DIFF_FRAGINFO));
    ++	init_color(r, s, "context", s->context_color,
    ++		diff_get_color(s->use_color, DIFF_CONTEXT));
    ++	init_color(r, s, "old", s->file_old_color,
    ++		diff_get_color(s->use_color, DIFF_FILE_OLD));
    ++	init_color(r, s, "new", s->file_new_color,
    ++		diff_get_color(s->use_color, DIFF_FILE_NEW));
      }
    + 
    + /*
     
      ## add-interactive.h ##
     @@ add-interactive.h: struct add_i_state {
    @@ add-interactive.h: struct add_i_state {
     +	char file_new_color[COLOR_MAXLEN];
      };
      
    - int init_add_i_state(struct repository *r, struct add_i_state *s);
    + void init_add_i_state(struct add_i_state *s, struct repository *r);
     
      ## add-patch.c ##
     @@ add-patch.c: struct hunk_header {
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
     -		reassemble_patch(s, file_diff, &s->buf);
     +		reassemble_patch(s, file_diff, 0, &s->buf);
      
    + 		discard_index(s->s.r->index);
      		setup_child_process(&cp, s, "apply", "--cached", NULL);
    - 		if (pipe_command(&cp, s->buf.buf, s->buf.len,
 59:  84e41328770 !  51:  9cd1a973bd2 built-in add -p: implement the 'g' ("goto") command
    @@ t/t3701-add-interactive.sh: test_expect_success 'split hunk setup' '
     +test_expect_success 'goto hunk' '
     +	test_when_finished "git reset" &&
     +	tr _ " " >expect <<-EOF &&
    -+	Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1:  -1,2 +1,3          +15
    ++	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1:  -1,2 +1,3          +15
     +	_ 2:  -2,4 +3,8          +21
     +	go to which hunk? @@ -1,2 +1,3 @@
     +	_10
     +	+15
     +	_20
    -+	Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
    ++	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
     +	EOF
     +	test_write_lines s y g 1 | git add -p >actual &&
     +	tail -n 7 <actual >actual.trimmed &&
 60:  59c02506c93 !  52:  5dbae4baa43 built-in add -p: implement the '/' ("search regex") command
    @@ t/t3701-add-interactive.sh: test_expect_success 'goto hunk' '
     +test_expect_success 'navigate to hunk via regex' '
     +	test_when_finished "git reset" &&
     +	tr _ " " >expect <<-EOF &&
    -+	Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@
    ++	(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@
     +	_10
     +	+15
     +	_20
    -+	Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
    ++	(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_
     +	EOF
     +	test_write_lines s y /1,2 | git add -p >actual &&
     +	tail -n 5 <actual >actual.trimmed &&
 61:  52d63a36de4 =  53:  f225674d25e built-in add -p: implement the 'q' ("quit") command
 62:  248a546205e =  54:  69efacc55ce built-in add -p: only show the applicable parts of the help text
 63:  84453baa7b1 !  55:  24df6a1c4e2 built-in add -p: show helpful hint when nothing can be staged
    @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
     -	size_t i;
     +	size_t i, binary_count = 0;
      
    - 	if (init_add_i_state(r, &s.s))
    - 		return error("Could not read `add -i` config");
    + 	init_add_i_state(&s.s, r);
    + 
     @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
      	}
      
 64:  172dc1ede3d !  56:  8fca5f8ad5e built-in add -p: prepare for patch modes other than "stage"
    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
      	for (i = 0; i < ps->nr; i++)
      		argv_array_push(&args, ps->items[i].original);
      
    +@@ add-patch.c: static void render_hunk(struct add_p_state *s, struct hunk *hunk,
    + 				- header->colored_extra_start;
    + 		}
    + 
    +-		new_offset += delta;
    ++		if (s->mode->is_reverse)
    ++			old_offset -= delta;
    ++		else
    ++			new_offset += delta;
    + 
    + 		strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
    + 			    old_offset, header->old_count,
     @@ add-patch.c: static int edit_hunk_manually(struct add_p_state *s, struct hunk *hunk)
      				"(context).\n"
      				"To remove '%c' lines, delete them.\n"
    @@ add-patch.c: static size_t display_hunks(struct add_p_state *s,
      N_("j - leave this hunk undecided, see next undecided hunk\n"
         "J - leave this hunk undecided, see next hunk\n"
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
    - 			prompt_mode_type = PROMPT_HUNK;
    - 
    + 			      (uintmax_t)hunk_index + 1,
    + 			      (uintmax_t)file_diff->hunk_nr);
      		color_fprintf(stdout, s->s.prompt_color,
     -			      _(prompt_mode[prompt_mode_type]), s->buf.buf);
     +			      _(s->mode->prompt_mode[prompt_mode_type]),
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
      			/*
      			 * Show only those lines of the remainder that are
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
    - 		strbuf_reset(&s->buf);
      		reassemble_patch(s, file_diff, 0, &s->buf);
      
    + 		discard_index(s->s.r->index);
     -		setup_child_process(&cp, s, "apply", "--cached", NULL);
     +		setup_child_process(&cp, s, "apply", NULL);
     +		argv_array_pushv(&cp.args, s->mode->apply);
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
      				 NULL, 0, NULL, 0))
     -			error(_("'git apply --cached' failed"));
     +			error(_("'git apply' failed"));
    - 		repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0);
    - 	}
    - 
    + 		if (!repo_read_index(s->s.r))
    + 			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
    + 						     1, NULL, NULL, NULL);
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
      	return quit;
      }
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
      	struct add_p_state s = {
      		{ r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
     @@ add-patch.c: int run_add_p(struct repository *r, const struct pathspec *ps)
    - 	if (init_add_i_state(r, &s.s))
    - 		return error("Could not read `add -i` config");
    + 
    + 	init_add_i_state(&s.s, r);
      
     +	s.mode = &patch_mode_stage;
     +	s.revision = revision;
     +
    - 	if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
    - 	    parse_diff(&s, ps) < 0) {
    - 		strbuf_release(&s.plain);
    + 	if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
    + 	    repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
    + 					 NULL, NULL, NULL) < 0 ||
     
      ## builtin/add.c ##
     @@ builtin/add.c: static void refresh(int verbose, const struct pathspec *pathspec)
 66:  01ca0692e71 !  57:  d09568bf856 built-in add -p: implement the "stash" and "reset" patch modes
    @@ add-patch.c: static struct patch_mode patch_mode_stage = {
      	unsigned long old_offset, old_count, new_offset, new_count;
      	/*
     @@ add-patch.c: int run_add_p(struct repository *r, enum add_p_mode mode,
    - 	if (init_add_i_state(r, &s.s))
    - 		return error("Could not read `add -i` config");
    + 
    + 	init_add_i_state(&s.s, r);
      
     -	s.mode = &patch_mode_stage;
     +	if (mode == ADD_P_STASH)
    @@ add-patch.c: int run_add_p(struct repository *r, enum add_p_mode mode,
     +		s.mode = &patch_mode_stage;
      	s.revision = revision;
      
    - 	if (repo_refresh_and_write_index(r, REFRESH_QUIET, 0) < 0 ||
    + 	if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
     
      ## builtin/add.c ##
     @@ builtin/add.c: int run_add_interactive(const char *revision, const char *patch_mode,
 65:  5931f6e9067 !  58:  96604247ec9 stash -p: respect the add.interactive.usebuiltin setting
    @@ Metadata
     Author: Johannes Schindelin <[email protected]>
     
      ## Commit message ##
    -    stash -p: respect the add.interactive.usebuiltin setting
    +    legacy stash -p: respect the add.interactive.usebuiltin setting
     
         As `git add` traditionally did not expose the `--patch=<mode>` modes via
    -    command-line options, `git stash` had to call `git add--interactive`
    -    directly.
    +    command-line options, the scripted version of `git stash` had to call
    +    `git add--interactive` directly.
     
         But this prevents the built-in `add -p` from kicking in, as
         `add--interactive` is the Perl script.
     
         So let's introduce support for an optional `<mode>` argument in `git add
    -    --patch[=<mode>]`, and use that in `git stash -p`, so that the built-in
    -    interactive add can do its job if configured.
    +    --patch[=<mode>]`, and use that in the scripted version of `git stash
    +    -p`, so that the built-in interactive add can do its job if configured.
     
         Signed-off-by: Johannes Schindelin <[email protected]>
     
 70:  8c47930a610 =  59:  92757acc16a built-in stash: use the built-in `git add -p` if so configured
 67:  3c7fe49a382 !  60:  a1706ffa892 built-in add -p: implement the "checkout" patch modes
    @@ add-patch.c: static int edit_hunk_loop(struct add_p_state *s,
      #define SUMMARY_LINE_WIDTH 80
      static void summarize_hunk(struct add_p_state *s, struct hunk *hunk,
     @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
    - 		strbuf_reset(&s->buf);
      		reassemble_patch(s, file_diff, 0, &s->buf);
      
    + 		discard_index(s->s.r->index);
     -		setup_child_process(&cp, s, "apply", NULL);
     -		argv_array_pushv(&cp.args, s->mode->apply);
     -		if (pipe_command(&cp, s->buf.buf, s->buf.len,
    @@ add-patch.c: static int patch_update_file(struct add_p_state *s,
     +					 NULL, 0, NULL, 0))
     +				error(_("'git apply' failed"));
     +		}
    - 		repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0);
    - 	}
    - 
    + 		if (!repo_read_index(s->s.r))
    + 			repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
    + 						     1, NULL, NULL, NULL);
     @@ add-patch.c: int run_add_p(struct repository *r, enum add_p_mode mode,
      			s.mode = &patch_mode_reset_head;
      		else
 68:  0b714f37cd9 =  61:  66aabfdd893 built-in add -p: implement the "worktree" patch modes
 69:  a4efb088886 =  62:  5ff37622ecc commit --interactive: make it work with the built-in `add -i`
 71:  50b9fd69852 !  63:  e7bbd99438b built-in add -p: support interactive.diffFilter
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: int init_add_i_state(struct repository *r, struct add_i_state *s)
    - 	strlcpy(s->file_new_color,
    - 		diff_get_color(s->use_color, DIFF_FILE_NEW), COLOR_MAXLEN);
    +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
    + 		diff_get_color(s->use_color, DIFF_FILE_OLD));
    + 	init_color(r, s, "new", s->file_new_color,
    + 		diff_get_color(s->use_color, DIFF_FILE_NEW));
    ++
    ++	FREE_AND_NULL(s->interactive_diff_filter);
    ++	git_config_get_string("interactive.difffilter",
    ++			      &s->interactive_diff_filter);
    ++}
    ++
    ++void clear_add_i_state(struct add_i_state *s)
    ++{
    ++	FREE_AND_NULL(s->interactive_diff_filter);
    ++	memset(s, 0, sizeof(*s));
    ++	s->use_color = -1;
    + }
      
    -+	free(s->interactive_diff_filter);
    -+	if (git_config_get_string("interactive.difffilter",
    -+				  &s->interactive_diff_filter))
    -+		s->interactive_diff_filter = NULL;
    + /*
    +@@ add-interactive.c: int run_add_i(struct repository *r, const struct pathspec *ps)
    + 	strbuf_release(&print_file_item_data.worktree);
    + 	strbuf_release(&header);
    + 	prefix_item_list_clear(&commands);
    ++	clear_add_i_state(&s);
      
    - 	return 0;
    + 	return res;
      }
     
      ## add-interactive.h ##
    @@ add-interactive.h: struct add_i_state {
     +	char *interactive_diff_filter;
      };
      
    - int init_add_i_state(struct repository *r, struct add_i_state *s);
    + void init_add_i_state(struct add_i_state *s, struct repository *r);
    ++void clear_add_i_state(struct add_i_state *s);
    + 
    + enum color_add_i {
    + 	COLOR_HEADER = 0,
     @@ add-interactive.h: enum color_add_i {
      	COLOR_RESET,
      };
    @@ add-patch.c: static int parse_diff(struct add_p_state *s, const struct pathspec
      	return 0;
      }
      
    +@@ add-patch.c: int run_add_p(struct repository *r, enum add_p_mode mode,
    + 	    parse_diff(&s, ps) < 0) {
    + 		strbuf_release(&s.plain);
    + 		strbuf_release(&s.colored);
    ++		clear_add_i_state(&s.s);
    + 		return -1;
    + 	}
    + 
    +@@ add-patch.c: int run_add_p(struct repository *r, enum add_p_mode mode,
    + 	strbuf_release(&s.buf);
    + 	strbuf_release(&s.plain);
    + 	strbuf_release(&s.colored);
    ++	clear_add_i_state(&s.s);
    + 	return 0;
    + }
 72:  d94657e5612 !  64:  0c950c27175 built-in add -p: handle diff.algorithm
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: int init_add_i_state(struct repository *r, struct add_i_state *s)
    - 				  &s->interactive_diff_filter))
    - 		s->interactive_diff_filter = NULL;
    - 
    -+	free(s->interactive_diff_algorithm);
    -+	if (git_config_get_string("diff.algorithm",
    -+				  &s->interactive_diff_algorithm))
    -+		s->interactive_diff_algorithm = NULL;
    +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
    + 	FREE_AND_NULL(s->interactive_diff_filter);
    + 	git_config_get_string("interactive.difffilter",
    + 			      &s->interactive_diff_filter);
     +
    - 	return 0;
    ++	FREE_AND_NULL(s->interactive_diff_algorithm);
    ++	git_config_get_string("diff.algorithm",
    ++			      &s->interactive_diff_algorithm);
      }
      
    + void clear_add_i_state(struct add_i_state *s)
    + {
    + 	FREE_AND_NULL(s->interactive_diff_filter);
    ++	FREE_AND_NULL(s->interactive_diff_algorithm);
    + 	memset(s, 0, sizeof(*s));
    + 	s->use_color = -1;
    + }
     
      ## add-interactive.h ##
     @@ add-interactive.h: struct add_i_state {
    @@ add-interactive.h: struct add_i_state {
     +	char *interactive_diff_filter, *interactive_diff_algorithm;
      };
      
    - int init_add_i_state(struct repository *r, struct add_i_state *s);
    + void init_add_i_state(struct add_i_state *s, struct repository *r);
     @@ add-interactive.h: enum color_add_i {
      };
      const char *get_add_i_color(enum color_add_i ix);
 73:  aed590f8dc9 =  65:  cf6734cbea3 terminal: make the code of disable_echo() reusable
 74:  001d469a795 =  66:  f9bbce6615a terminal: accommodate Git for Windows' default terminal
 75:  1c0af6ef090 =  67:  f0df8ce4eb7 terminal: add a new function to read a single keystroke
 76:  1a7378a19be !  68:  068db526a6f built-in add -p: respect the `interactive.singlekey` config setting
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## add-interactive.c ##
    -@@ add-interactive.c: int init_add_i_state(struct repository *r, struct add_i_state *s)
    - 				  &s->interactive_diff_algorithm))
    - 		s->interactive_diff_algorithm = NULL;
    - 
    -+	if (git_config_get_bool("interactive.singlekey",
    -+				&s->use_single_key))
    -+		s->use_single_key = 0;
    +@@ add-interactive.c: void init_add_i_state(struct add_i_state *s, struct repository *r)
    + 	FREE_AND_NULL(s->interactive_diff_algorithm);
    + 	git_config_get_string("diff.algorithm",
    + 			      &s->interactive_diff_algorithm);
     +
    - 	return 0;
    ++	git_config_get_bool("interactive.singlekey", &s->use_single_key);
      }
      
    + void clear_add_i_state(struct add_i_state *s)
     
      ## add-interactive.h ##
     @@ add-interactive.h: struct add_i_state {
 77:  57ee427d9e8 !  69:  4fed9c991e5 built-in add -p: handle Escape sequences in interactive.singlekey mode
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## compat/terminal.c ##
    -@@
    - #include "strbuf.h"
    - #include "run-command.h"
    - #include "string-list.h"
    -+#include "argv-array.h"
    - 
    - #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
    - 
     @@ compat/terminal.c: static int enable_non_canonical(void)
      	return disable_bits(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
      }
 78:  7e8faa0a98e !  70:  d7fa28e0f51 built-in add -p: handle Escape sequences more efficiently
    @@ Commit message
     
      ## compat/terminal.c ##
     @@
    + #include "strbuf.h"
      #include "run-command.h"
      #include "string-list.h"
    - #include "argv-array.h"
     +#include "hashmap.h"
      
      #if defined(HAVE_DEV_TTY) || defined(GIT_WINDOWS_NATIVE)
    @@ compat/terminal.c: char *git_terminal_prompt(const char *prompt, int echo)
     +				p[0] = '^';
     +				p[1] = '[';
     +				FLEX_ALLOC_MEM(e, sequence, p, comma - p);
    -+				hashmap_entry_init(e, strhash(e->sequence));
    -+				hashmap_add(&sequences, e);
    ++				hashmap_entry_init(&e->entry,
    ++						   strhash(e->sequence));
    ++				hashmap_add(&sequences, &e->entry);
     +			}
     +			if (!*eol)
     +				break;
 79:  6bf71f296ac =  71:  2f4eda1b087 ci: include the built-in `git add -i` in the `linux-gcc` job
 80:  ff0d6ef29ce =  72:  c34f32d49c6 t3904: fix incorrect demonstration of a bug
 81:  b46246131bc =  73:  72ae41e5f5a stash -p: (partially) fix bug concerning split hunks
 82:  d6e38aa047f <   -:  ----------- push: do not pretend to return `int` from `die_push_simple()`
 83:  1b9fc01e7e4 <   -:  ----------- msvc: avoid using minus operator on unsigned types
 84:  53696ea66ac <   -:  ----------- winansi: use FLEX_ARRAY to avoid compiler warning
 85:  34ae8f134ec <   -:  ----------- compat/win32/path-utils.h: add #include guards
 86:  39d0e0545f9 <   -:  ----------- msvc: ignore some libraries when linking
 87:  9e78f8b38ab <   -:  ----------- msvc: handle DEVELOPER=1
 88:  e19a1ff443f <   -:  ----------- msvc: work around a bug in GetEnvironmentVariable()
 89:  7767370a2c6 <   -:  ----------- ci: really use shallow clones on Azure Pipelines
 90:  0d53ffa8c0b <   -:  ----------- ci: also test with MS Visual C on Azure Pipelines
 92:  b1366175182 =  74:  75172259724 gitk: prevent overly long command lines
 91:  9eac299e912 =  75:  3eac2776094 mingw: change core.fsyncObjectFiles = 1 by default
  -:  ----------- >  76:  8a3a3677f52 gitk: Escape file paths before piping to git log
 93:  9099b7763fe =  77:  97e21fb230c t9001, t9116: avoid pipes
 98:  5eb3f484ac3 !  78:  61ad94d0b8e clean: do not traverse mount points
    @@ compat/mingw.c: pid_t waitpid(pid_t pid, int *status, int options)
     +{
     +	WIN32_FIND_DATAW findbuf = { 0 };
     +	HANDLE handle;
    -+	wchar_t wfilename[MAX_LONG_PATH];
    -+	int wlen = xutftowcs_long_path(wfilename, path->buf);
    ++	wchar_t wfilename[MAX_PATH];
    ++	int wlen = xutftowcs_path(wfilename, path->buf);
     +	if (wlen < 0)
     +		die(_("could not get long path for '%s'"), path->buf);
     +
    @@ compat/mingw.h: static inline void convert_slashes(char *path)
     +int mingw_is_mount_point(struct strbuf *path);
     +#define is_mount_point mingw_is_mount_point
      #define PATH_SEP ';'
    - extern char *mingw_query_user_email(void);
    + char *mingw_query_user_email(void);
      #define query_user_email mingw_query_user_email
     
      ## git-compat-util.h ##
    @@ git-compat-util.h: static inline char *git_find_last_dir_sep(const char *path)
     
      ## path.c ##
     @@ path.c: char *strip_path_suffix(const char *path, const char *suffix)
    - 	return xstrndup(path, chomp_trailing_dir_sep(path, path_len));
    + 	return offset == -1 ? NULL : xstrndup(path, offset);
      }
      
     +int is_mount_point_via_stat(struct strbuf *path)
 94:  4e9e135e7b9 =  79:  e38285ba997 mingw: explicitly `fflush` stdout
 95:  d46e64c2aeb =  80:  4713be92df0 mingw: make is_hidden tests in t0001/t5611 more robust
 96:  22958f7d672 =  81:  7d2b6fe6a5a Help debugging with MSys2 by optionally executing bash with strace
 99:  e23c584977f !  82:  cfaed709844 clean: remove mount points when possible
    @@ compat/mingw.h: static inline void convert_slashes(char *path)
      #define is_mount_point mingw_is_mount_point
     +#define CAN_UNLINK_MOUNT_POINTS 1
      #define PATH_SEP ';'
    - extern char *mingw_query_user_email(void);
    + char *mingw_query_user_email(void);
      #define query_user_email mingw_query_user_email
     
      ## t/t7300-clean.sh ##
  -:  ----------- >  83:  058c8faae5e mingw: move Git for Windows' system config where users expect it
  -:  ----------- >  84:  eff1290ede9 vcpkg_install: detect lack of Git
  -:  ----------- >  85:  aa6df6967dd config: normalize the path of the system gitconfig
100:  884154638bf =  86:  0f163ab3a6b mingw: cope with the Isilon network file system
  -:  ----------- >  87:  c55ee6efeb2 config.mak.uname: PCRE1 cleanup
  -:  ----------- >  88:  3d170b096ee rebase -r: let `label` generate safer labels
  -:  ----------- >  89:  824b7dd44d0 vcpkg_install: add comment regarding slow network connections
125:  c318e1f9f6c !  90:  81b74a86626 mingw: demonstrate that all file handles are inherited by child processes
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## t/helper/test-run-command.c ##
    -@@ t/helper/test-run-command.c: static int task_finished(int result,
    - 	return 1;
    +@@ t/helper/test-run-command.c: static int testsuite(int argc, const char **argv)
    + 	return !!ret;
      }
      
     +static int inherit_handle(const char *argv0)
    @@ t/helper/test-run-command.c: static int task_finished(int result,
      int cmd__run_command(int argc, const char **argv)
      {
      	struct child_process proc = CHILD_PROCESS_INIT;
    - 	int jobs;
    +@@ t/helper/test-run-command.c: int cmd__run_command(int argc, const char **argv)
      
    -+	if (argc < 2)
    -+		return 1;
    + 	if (argc > 1 && !strcmp(argv[1], "testsuite"))
    + 		exit(testsuite(argc - 1, argv + 1));
     +	if (!strcmp(argv[1], "inherited-handle"))
     +		exit(inherit_handle(argv[0]));
     +	if (!strcmp(argv[1], "inherited-handle-child"))
     +		exit(inherit_handle_child());
    -+
    + 
      	if (argc < 3)
      		return 1;
    - 	while (!strcmp(argv[1], "env")) {
     
      ## t/t0061-run-command.sh ##
     @@ t/t0061-run-command.sh: cat >hello-script <<-EOF
126:  35bb7d736a1 =  91:  604477e836d mingw: work around incorrect standard handles
127:  041d54cdec4 =  92:  6fa32478ab7 mingw: spawned processes need to inherit only standard handles
 97:  52b7ae3d676 !  93:  cba298e17c5 Build Python stuff with MSys2
    @@ Metadata
     Author: Johannes Schindelin <[email protected]>
     
      ## Commit message ##
    -    Build Python stuff with MSys2
    +    mingw: include the Python parts in the build
    +
    +    While Git for Windows does not _ship_ Python (in order to save on
    +    bandwidth), MSYS2 provides very fine Python interpreters that users can
    +    easily take advantage of, by using Git for Windows within its SDK.
     
         Signed-off-by: Johannes Schindelin <[email protected]>
     
    @@ config.mak.uname: else
      		NO_CURL =
      		USE_NED_ALLOCATOR = YesPlease
     +		NO_PYTHON =
    - 	else
    - 		COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO
    - 		NO_CURL = YesPlease
    + 		ifeq (/mingw64,$(subst 32,64,$(prefix)))
    + 			# Move system config into top-level /etc/
    + 			ETC_GITCONFIG = ../etc/gitconfig
130:  0209b061167 !  94:  a86420d323c mingw: restrict file handle inheritance only on Windows 7 and later
    @@ compat/mingw.c: static pid_t mingw_spawnve_fd(const char *cmd, const char **argv
      	PROCESS_INFORMATION pi;
      	LPPROC_THREAD_ATTRIBUTE_LIST attr_list = NULL;
     @@ compat/mingw.c: static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
    + 	const char *(*quote_arg)(const char *arg) =
      		is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
    - 	const char *strace_env;
      
     +	if (restrict_handle_inheritance < 0)
     +		restrict_handle_inheritance = core_restrict_inherited_handles;
  -:  ----------- >  95:  0e2e99d15c2 Config option to disable side-band-64k for transport
  -:  ----------- >  96:  06c0ae22b44 vreportf(): avoid relying on stdio buffering
  -:  ----------- >  97:  aa5ef349fd9 update-index: optionally leave skip-worktree entries alone
  -:  ----------- >  98:  bd7aed58e9a stash: handle staged changes in skip-worktree files correctly
101:  a2b93fecb3e =  99:  0b4fa947f50 Revert "git-gui: set GIT_DIR and GIT_WORK_TREE after setup"
102:  8440c57b1a1 = 100:  770e0134bf5 git-gui: provide question helper for retry fallback on Windows
103:  e06c738d602 = 101:  2b92c103984 git gui: set GIT_ASKPASS=git-gui--askpass if not set yet
104:  5133e74c649 <   -:  ----------- git-gui (Windows): use git-bash.exe if it is available
105:  7b571fef14f = 102:  918f25297bc git-gui--askyesno: fix funny text wrapping
107:  db957206029 = 103:  78b8a99e7c6 git-gui--askyesno: allow overriding the window title
109:  371e452aacc = 104:  1982635a6f8 respect core.hooksPath, falling back to .git/hooks
110:  5afc99b1b32 = 105:  0bc3f7b50a9 git-gui: correctly restore GIT_DIR after invoking gitk
111:  64130f3fabe = 106:  4f317473bd0 git-gui--askyesno (mingw): use Git for Windows' icon, if available
106:  cfba13a827e = 107:  03ef1958b31 gitk: Unicode file name support
108:  768271c210e = 108:  727957568ec gitk: Use an external icon file on Windows
112:  474ffbf39b6 = 109:  cf280f013ff gitk: fix arrow keys in input fields with Tcl/Tk >= 8.6
113:  3ee6c136300 = 110:  a00f9a0bd48 gitk: make the "list references" default window width wider
114:  84404c80afd <   -:  ----------- Add a Code of Conduct
129:  46e84c9c3d2 = 111:  e42dd387168 Win32: make FILETIME conversion functions public
132:  97d917b2cbb = 112:  336909e9142 Win32: dirent.c: Move opendir down
133:  5de880a9653 = 113:  59f2eed540e Win32: Make the dirent implementation pluggable
134:  8629e91eda0 = 114:  61ce3d5e48c Win32: make the lstat implementation pluggable
135:  d78df1b9343 ! 115:  a9c8d5005f3 add infrastructure for read-only file system level caches
    @@ builtin/commit.c: int cmd_status(int argc, const char **argv, const char *prefix
     
      ## compat/mingw.c ##
     @@ compat/mingw.c: enum hide_dotfiles_type {
    - 
    + static int core_restrict_inherited_handles = -1;
      static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
      static char *unset_environment_variables;
     +int core_fscache;
    @@ compat/mingw.h: typedef _sigset_t sigset_t;
      
     +extern int core_fscache;
     +
    - extern int mingw_core_config(const char *var, const char *value, void *cb);
    + int mingw_core_config(const char *var, const char *value, void *cb);
      #define platform_core_config mingw_core_config
      
     
128:  a4913126462 = 116:  cdc35ab6acd Allow `add -p` and `add -i` with a large number of files
136:  f05e7b49af6 ! 117:  09bb1be0aef Win32: add a cache below mingw's lstat and dirent implementations
    @@ compat/win32/fscache.c (new)
     +	fse->list = list;
     +	fse->name = name;
     +	fse->len = len;
    -+	hashmap_entry_init(fse, fsentry_hash(fse));
    ++	hashmap_entry_init(&fse->ent, fsentry_hash(fse));
     +}
     +
     +/*
    @@ compat/win32/fscache.c (new)
     +		fse = fse->list;
     +
     +	for (; fse; fse = fse->next)
    -+		hashmap_add(&map, fse);
    ++		hashmap_add(&map, &fse->ent);
     +}
     +
     +/*
    @@ compat/win32/fscache.c (new)
     + */
     +static void fscache_clear(void)
     +{
    -+	hashmap_free(&map, 1);
    ++	hashmap_free_entries(&map, struct fsentry, ent);
     +	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
     +}
     +
    @@ compat/win32/fscache.c (new)
     +
     +	EnterCriticalSection(&mutex);
     +	/* check if entry is in cache */
    -+	fse = hashmap_get(&map, key, NULL);
    ++	fse = hashmap_get_entry(&map, key, ent, NULL);
     +	if (fse) {
     +		fsentry_addref(fse);
     +		LeaveCriticalSection(&mutex);
    @@ compat/win32/fscache.c (new)
     +	}
     +	/* if looking for a file, check if directory listing is in cache */
     +	if (!fse && key->list) {
    -+		fse = hashmap_get(&map, key->list, NULL);
    ++		fse = hashmap_get_entry(&map, key->list, ent, NULL);
     +		if (fse) {
     +			LeaveCriticalSection(&mutex);
     +			/* dir entry without file entry -> file doesn't exist */
    @@ compat/win32/fscache.c (new)
     +
     +	EnterCriticalSection(&mutex);
     +	/* add directory listing if it hasn't been added by some other thread */
    -+	if (!hashmap_get(&map, key, NULL))
    ++	if (!hashmap_get_entry(&map, key, ent, NULL))
     +		fscache_add(fse);
     +
     +	/* lookup file entry if requested (fse already points to directory) */
     +	if (key->list)
    -+		fse = hashmap_get(&map, key, NULL);
    ++		fse = hashmap_get_entry(&map, key, ent, NULL);
     +
     +	/* return entry or ENOENT */
     +	if (fse)
    @@ compat/win32/fscache.h (new)
     +#endif
     
      ## config.mak.uname ##
    -@@ config.mak.uname: ifeq ($(uname_S),Windows)
    +@@ config.mak.uname: endif
      		compat/win32/path-utils.o \
      		compat/win32/pthread.o compat/win32/syslog.o \
      		compat/win32/trace2_win32_process_info.o \
131:  0618448d9d3 = 118:  7d3c4fa2a16 t3701: verify that we can add *lots* of files interactively
137:  74a172e9621 ! 119:  d626c4026e5 fscache: load directories only once
    @@ compat/win32/fscache.c: static inline int fscache_enabled(const char *path)
     + */
     +static struct fsentry *fscache_get_wait(struct fsentry *key)
     +{
    -+	struct fsentry *fse = hashmap_get(&map, key, NULL);
    ++	struct fsentry *fse = hashmap_get_entry(&map, key, ent, NULL);
     +
     +	/* return if its a 'real' entry (future entries have refcnt == 0) */
     +	if (!fse || fse->list || fse->u.refcnt)
    @@ compat/win32/fscache.c: static inline int fscache_enabled(const char *path)
     +	EnterCriticalSection(&mutex);
     +
     +	/* repeat cache lookup */
    -+	return hashmap_get(&map, key, NULL);
    ++	return hashmap_get_entry(&map, key, ent, NULL);
     +}
     +
      /*
    @@ compat/win32/fscache.c: static inline int fscache_enabled(const char *path)
      
      	EnterCriticalSection(&mutex);
      	/* check if entry is in cache */
    --	fse = hashmap_get(&map, key, NULL);
    +-	fse = hashmap_get_entry(&map, key, ent, NULL);
     +	fse = fscache_get_wait(key);
      	if (fse) {
      		fsentry_addref(fse);
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
      	}
      	/* if looking for a file, check if directory listing is in cache */
      	if (!fse && key->list) {
    --		fse = hashmap_get(&map, key->list, NULL);
    +-		fse = hashmap_get_entry(&map, key->list, ent, NULL);
     +		fse = fscache_get_wait(key->list);
      		if (fse) {
      			LeaveCriticalSection(&mutex);
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
     +	future = key->list ? key->list : key;
     +	future->next = NULL;
     +	future->u.refcnt = 0;
    -+	hashmap_add(&map, future);
    ++	hashmap_add(&map, &future->ent);
     +
      	/* create the directory listing (outside mutex!) */
      	LeaveCriticalSection(&mutex);
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
     +	EnterCriticalSection(&mutex);
     +
     +	/* remove future entry and signal waiting threads */
    -+	hashmap_remove(&map, future, NULL);
    ++	hashmap_remove(&map, &future->ent, NULL);
     +	waiter = future->next;
     +	while (waiter) {
     +		HANDLE h = waiter->u.hwait;
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
      
     -	EnterCriticalSection(&mutex);
     -	/* add directory listing if it hasn't been added by some other thread */
    --	if (!hashmap_get(&map, key, NULL))
    +-	if (!hashmap_get_entry(&map, key, ent, NULL))
     -		fscache_add(fse);
     +	/* add directory listing to the cache */
     +	fscache_add(fse);
138:  e3a2f77b70a = 120:  b1a5b990386 fscache: add key for GIT_TRACE_FSCACHE
139:  72e2cfa017d ! 121:  8343e4518e6 fscache: remember not-found directories
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
     +			fse = fsentry_alloc(key->list->list,
     +					    key->list->name, key->list->len);
     +			fse->st_mode = 0;
    -+			hashmap_add(&map, fse);
    ++			hashmap_add(&map, &fse->ent);
     +		}
      		LeaveCriticalSection(&mutex);
      		return NULL;
      	}
     @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
      	if (key->list)
    - 		fse = hashmap_get(&map, key, NULL);
    + 		fse = hashmap_get_entry(&map, key, ent, NULL);
      
     +	if (fse && !fse->st_mode)
     +		fse = NULL; /* non-existing directory */
140:  cc7eb03f6f2 = 122:  6d94f92e3b8 fscache: add a test for the dir-not-found optimization
141:  5f80a94b4a7 = 123:  4968479f4f9 add: use preload-index and fscache for performance
142:  56fb329b15a ! 124:  e57967521fa dir.c: make add_excludes aware of fscache during status
    @@ compat/win32/fscache.h
      
     
      ## dir.c ##
    -@@ dir.c: static int add_excludes(const char *fname, const char *base, int baselen,
    +@@ dir.c: static int add_patterns(const char *fname, const char *base, int baselen,
      	size_t size = 0;
      	char *buf;
      
143:  f7128a113fe ! 125:  85520e081c8 fscache: make fscache_enabled() public
    @@ compat/win32/fscache.h
      int fscache_lstat(const char *file_name, struct stat *buf);
     
      ## dir.c ##
    -@@ dir.c: static int add_excludes(const char *fname, const char *base, int baselen,
    +@@ dir.c: static int add_patterns(const char *fname, const char *base, int baselen,
      	size_t size = 0;
      	char *buf;
      
144:  a812574d49c ! 126:  ad48a7a27c8 dir.c: regression fix for add_excludes with fscache
    @@ Commit message
         Signed-off-by: Jeff Hostetler <[email protected]>
     
      ## dir.c ##
    -@@ dir.c: static int add_excludes(const char *fname, const char *base, int baselen,
    +@@ dir.c: static int add_patterns(const char *fname, const char *base, int baselen,
      	size_t size = 0;
      	char *buf;
      
    @@ dir.c: static int add_excludes(const char *fname, const char *base, int baselen,
      	if (is_fscache_enabled(fname)) {
      		if (lstat(fname, &st) < 0) {
      			fd = -1;
    -@@ dir.c: static int add_excludes(const char *fname, const char *base, int baselen,
    +@@ dir.c: static int add_patterns(const char *fname, const char *base, int baselen,
      			fd = open(fname, O_RDONLY);
      			if (fd < 0)
      				warn_on_fopen_errors(fname);
145:  a5dc3f4b37b = 127:  999be243603 fetch-pack.c: enable fscache for stats under .git/objects
146:  0533f78233c = 128:  cb258e76f25 checkout.c: enable fscache for checkout again
147:  e40e7c554e6 = 129:  27bbac59584 Enable the filesystem cache (fscache) in refresh_index().
148:  fe4c131d211 = 130:  7e66a2f9d0b fscache: use FindFirstFileExW to avoid retrieving the short name
149:  237265d0382 = 131:  0fca9c29f3d status: disable and free fscache at the end of the status command
150:  a2c6dc8eff2 = 132:  c91f40e1bdd fscache: add GIT_TEST_FSCACHE support
151:  65428906389 = 133:  807a7b713e0 At the end of the add command, disable and free the fscache so that we don't leak the memory and so that we can dump the fscache statistics.
152:  05789f52bc9 ! 134:  5c5fe682ac7 fscache: add fscache hit statistics
    @@ compat/win32/fscache.c: static int initialized;
      /*
     @@ compat/win32/fscache.c: static void fscache_clear(void)
      {
    - 	hashmap_free(&map, 1);
    + 	hashmap_free_entries(&map, struct fsentry, ent);
      	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
     +	lstat_requests = opendir_requests = 0;
     +	fscache_misses = fscache_requests = 0;
153:  d9c6a28289c = 135:  d66aae6ac38 mem_pool: add GIT_TRACE_MEMPOOL support
154:  747606d8f6e = 136:  83632369822 fscache: fscache takes an initial size
155:  9ef4fba9f96 ! 137:  57fab21952f fscache: update fscache to be thread specific instead of global
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
      		fse = fse->list;
      
      	for (; fse; fse = fse->next)
    --		hashmap_add(&map, fse);
    -+		hashmap_add(&cache->map, fse);
    +-		hashmap_add(&map, &fse->ent);
    ++		hashmap_add(&cache->map, &fse->ent);
      }
      
      /*
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
     -static void fscache_clear(void)
     +static void fscache_clear(struct fscache *cache)
      {
    --	hashmap_free(&map, 1);
    +-	hashmap_free_entries(&map, struct fsentry, ent);
     -	hashmap_init(&map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
     -	lstat_requests = opendir_requests = 0;
     -	fscache_misses = fscache_requests = 0;
    -+	hashmap_free(&cache->map, 1);
    ++	hashmap_free_entries(&cache->map, struct fsentry, ent);
     +	hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
     +	cache->lstat_requests = cache->opendir_requests = 0;
     +	cache->fscache_misses = cache->fscache_requests = 0;
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
     -static struct fsentry *fscache_get_wait(struct fsentry *key)
     +int fscache_enabled(const char *path)
      {
    --	struct fsentry *fse = hashmap_get(&map, key, NULL);
    +-	struct fsentry *fse = hashmap_get_entry(&map, key, ent, NULL);
     -
     -	/* return if its a 'real' entry (future entries have refcnt == 0) */
     -	if (!fse || fse->list || fse->u.refcnt)
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
     +	struct fscache *cache = fscache_getcache();
      
     -	/* repeat cache lookup */
    --	return hashmap_get(&map, key, NULL);
    +-	return hashmap_get_entry(&map, key, ent, NULL);
     +	return cache ? do_fscache_enabled(cache, path) : 0;
      }
      
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
     +	cache->fscache_requests++;
      	/* check if entry is in cache */
     -	fse = fscache_get_wait(key);
    -+	fse = hashmap_get(&cache->map, key, NULL);
    ++	fse = hashmap_get_entry(&cache->map, key, ent, NULL);
      	if (fse) {
      		if (fse->st_mode)
      			fsentry_addref(fse);
    @@ compat/win32/fscache.c: static struct fsentry *fsentry_create_list(const struct
      	/* if looking for a file, check if directory listing is in cache */
      	if (!fse && key->list) {
     -		fse = fscache_get_wait(key->list);
    -+		fse = hashmap_get(&cache->map, key->list, NULL);
    ++		fse = hashmap_get_entry(&cache->map, key->list, ent, NULL);
      		if (fse) {
     -			LeaveCriticalSection(&mutex);
      			/*
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
     -	future = key->list ? key->list : key;
     -	future->next = NULL;
     -	future->u.refcnt = 0;
    --	hashmap_add(&map, future);
    +-	hashmap_add(&map, &future->ent);
     -
     -	/* create the directory listing (outside mutex!) */
     -	LeaveCriticalSection(&mutex);
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
     -	EnterCriticalSection(&mutex);
     -
     -	/* remove future entry and signal waiting threads */
    --	hashmap_remove(&map, future, NULL);
    +-	hashmap_remove(&map, &future->ent, NULL);
     -	waiter = future->next;
     -	while (waiter) {
     -		HANDLE h = waiter->u.hwait;
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
      			fse = fsentry_alloc(key->list->list,
      					    key->list->name, key->list->len);
      			fse->st_mode = 0;
    --			hashmap_add(&map, fse);
    -+			hashmap_add(&cache->map, fse);
    +-			hashmap_add(&map, &fse->ent);
    ++			hashmap_add(&cache->map, &fse->ent);
      		}
     -		LeaveCriticalSection(&mutex);
      		return NULL;
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fsentry *key)
      
      	/* lookup file entry if requested (fse already points to directory) */
      	if (key->list)
    --		fse = hashmap_get(&map, key, NULL);
    -+		fse = hashmap_get(&cache->map, key, NULL);
    +-		fse = hashmap_get_entry(&map, key, ent, NULL);
    ++		fse = hashmap_get_entry(&cache->map, key, ent, NULL);
      
      	if (fse && !fse->st_mode)
      		fse = NULL; /* non-existing directory */
156:  78291571cbe ! 138:  bd0bc59c41b fscache: teach fscache to use mempool
    @@ compat/win32/fscache.c: static void fscache_add(struct fscache *cache, struct fs
       */
      static void fscache_clear(struct fscache *cache)
      {
    --	hashmap_free(&cache->map, 1);
    +-	hashmap_free_entries(&cache->map, struct fsentry, ent);
     +	mem_pool_discard(cache->mem_pool, 0);
     +	cache->mem_pool = NULL;
     +	mem_pool_init(&cache->mem_pool, 0);
    -+	hashmap_free(&cache->map, 0);
    ++	hashmap_free(&cache->map);
      	hashmap_init(&cache->map, (hashmap_cmp_fn)fsentry_cmp, NULL, 0);
      	cache->lstat_requests = cache->opendir_requests = 0;
      	cache->fscache_misses = cache->fscache_requests = 0;
    @@ compat/win32/fscache.c: static struct fsentry *fscache_get(struct fscache *cache
     +			fse = fsentry_alloc(cache, key->list->list,
      					    key->list->name, key->list->len);
      			fse->st_mode = 0;
    - 			hashmap_add(&cache->map, fse);
    + 			hashmap_add(&cache->map, &fse->ent);
     @@ compat/win32/fscache.c: int fscache_enable(size_t initial_size)
      		 * '4' was determined empirically by testing several repos
      		 */
    @@ compat/win32/fscache.c: void fscache_disable(void)
      			cache->fscache_requests, cache->fscache_misses);
     -		fscache_clear(cache);
     +		mem_pool_discard(cache->mem_pool, 0);
    -+		hashmap_free(&cache->map, 0);
    ++		hashmap_free(&cache->map);
      		free(cache);
      	}
      
157:  8705e6e41f3 = 139:  a1d2c9be32e fscache: make fscache_enable() thread safe
158:  5f1f3dad60d = 140:  2e8dc106fb7 fscache: teach fscache to use NtQueryDirectoryFile
  -:  ----------- > 141:  ea8740fb4a5 unpack-trees: enable fscache for sparse-checkout
160:  222de2e757f = 142:  713b0d96c76 fscache: remember the reparse tag for each entry
161:  10f8902b513 ! 143:  713872994a6 fscache: implement an FSCache-aware is_mount_point()
    @@ compat/mingw.h: static inline void convert_slashes(char *path)
     +#define is_mount_point win32_is_mount_point
      #define CAN_UNLINK_MOUNT_POINTS 1
      #define PATH_SEP ';'
    - extern char *mingw_query_user_email(void);
    + char *mingw_query_user_email(void);
     
      ## compat/win32/fscache.c ##
     @@ compat/win32/fscache.c: int fscache_enable(size_t initial_size)
162:  62f1e89ced5 ! 144:  b66730e63ea clean: make use of FSCache
    @@ builtin/clean.c: int cmd_clean(int argc, const char **argv, const char *prefix)
      	if (!ignored)
      		setup_standard_excludes(&dir);
     @@ builtin/clean.c: int cmd_clean(int argc, const char **argv, const char *prefix)
    - 		strbuf_reset(&abs_path);
    + 		}
      	}
      
     +	disable_fscache();
163:  66f98eff961 = 145:  049137857a1 pack-objects (mingw): demonstrate a segmentation fault with large deltas
164:  d58321c4251 ! 146:  126df7e797f Win32: support long paths
    @@ Documentation/config/core.txt: core.fscache::
      	names that need to be unset before spawning any other process.
     
      ## compat/mingw.c ##
    -@@ compat/mingw.c: enum hide_dotfiles_type {
    +@@ compat/mingw.c: static int core_restrict_inherited_handles = -1;
      static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
      static char *unset_environment_variables;
      int core_fscache;
    @@ compat/mingw.c: int mingw_raise(int sig)
      		return -1;
      
      	if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
    +@@ compat/mingw.c: int mingw_is_mount_point(struct strbuf *path)
    + {
    + 	WIN32_FIND_DATAW findbuf = { 0 };
    + 	HANDLE handle;
    +-	wchar_t wfilename[MAX_PATH];
    +-	int wlen = xutftowcs_path(wfilename, path->buf);
    ++	wchar_t wfilename[MAX_LONG_PATH];
    ++	int wlen = xutftowcs_long_path(wfilename, path->buf);
    + 	if (wlen < 0)
    + 		die(_("could not get long path for '%s'"), path->buf);
    + 
     @@ compat/mingw.c: static void setup_windows_environment(void)
      	}
      }
    @@ compat/mingw.c: int wmain(int argc, const wchar_t **wargv)
      	/* invoke the real main() using our utf8 version of argv. */
      	exit_status = main(argc, argv);
      
    -@@ compat/mingw.c: int uname(struct utsname *buf)
    -  */
    - static int validate_system_file_ownership(const char *path)
    - {
    --	WCHAR wpath[MAX_PATH];
    -+	WCHAR wpath[MAX_LONG_PATH];
    - 	PSID owner_sid = NULL;
    - 	PSECURITY_DESCRIPTOR descriptor = NULL;
    - 	HANDLE token;
    -@@ compat/mingw.c: static int validate_system_file_ownership(const char *path)
    - 	DWORD err, len;
    - 	int ret;
    - 
    --	if (xutftowcs_path(wpath, path) < 0)
    -+	if (xutftowcs_long_path(wpath, path) < 0)
    - 		return -1;
    - 
    - 	err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT,
     
      ## compat/mingw.h ##
     @@ compat/mingw.h: typedef _sigset_t sigset_t;
    @@ compat/mingw.h: typedef _sigset_t sigset_t;
      extern int core_fscache;
     +extern int core_long_paths;
      
    - extern int mingw_core_config(const char *var, const char *value, void *cb);
    + int mingw_core_config(const char *var, const char *value, void *cb);
      #define platform_core_config mingw_core_config
    -@@ compat/mingw.h: extern const char *program_data_config(void);
    +@@ compat/mingw.h: char *mingw_query_user_email(void);
      #include <inttypes.h>
      #endif
      
165:  ba4bf83e6e9 = 147:  2c7fbf37321 Win32: fix 'lstat("dir/")' with long paths
166:  36210dea31c = 148:  5581269ff66 mingw: ensure that core.longPaths is handled *always*
167:  4a6d0bfb632 = 149:  2f0b09e291c mingw: Support `git_terminal_prompt` with more terminals
168:  50903db5b2d = 150:  453231634e2 compat/terminal.c: only use the Windows console if bash 'read -r' fails
169:  6eba6c3c3e4 = 151:  76c8177e645 mingw (git_terminal_prompt): do fall back to CONIN$/CONOUT$ method
170:  34cac403c59 = 152:  870287c05f6 Unbreak interactive GPG prompt upon signing
171:  5cfba60563e = 153:  e0b1087de8c mingw: ensure valid CTYPE
172:  06226e0d1d8 = 154:  f8511c622c0 mingw: disable t9020
173:  5f28e2e3752 = 155:  baca97a4666 strbuf_readlink: don't call readlink twice if hint is the exact link size
174:  a846050b206 = 156:  26797c7ea51 strbuf_readlink: support link targets that exceed PATH_MAX
175:  35d7b9afe10 = 157:  08f5b02081e lockfile.c: use is_dir_sep() instead of hardcoded '/' checks
176:  fa7b16e4de9 = 158:  1d5fa1d362e Win32: don't call GetFileAttributes twice in mingw_lstat()
177:  65a8b7d8e5b = 159:  1ba2e8a4c18 Win32: implement stat() with symlink support
178:  222076fc83b = 160:  0acbe71e1b2 Win32: remove separate do_lstat() function
179:  ab01a83913a = 161:  e9a908f246e Win32: let mingw_lstat() error early upon problems with reparse points
180:  1c21066608f = 162:  08396d32879 Win32: teach fscache and dirent about symlinks
181:  df6c4012021 = 163:  5328e3973d2 Win32: lstat(): return adequate stat.st_size for symlinks
182:  372f4d5c679 = 164:  42299509a8a Win32: factor out retry logic
183:  6f88e75226d = 165:  3e224f8752e Win32: change default of 'core.symlinks' to false
184:  91ec6cebdf6 = 166:  19d9fbe0ec3 Win32: add symlink-specific error codes
185:  0f1a2a7ff8f = 167:  b501ce5870f Win32: mingw_unlink: support symlinks to directories
186:  70bfb4364c9 = 168:  54dab2d75d9 Win32: mingw_rename: support renaming symlinks
187:  32ffa526a2b = 169:  ec22e46b340 Win32: mingw_chdir: change to symlink-resolved directory
188:  b16097ddd4e ! 170:  3274499e02c Win32: implement readlink()
    @@ Commit message
     
      ## compat/mingw.c ##
     @@
    + #include "win32.h"
    + #include <conio.h>
      #include <wchar.h>
    - #include <aclapi.h>
    - #include <sddl.h>
     +#include <winioctl.h>
      #include "../strbuf.h"
      #include "../run-command.h"
189:  899ea12ffc6 = 171:  f56076a2390 Win32: implement basic symlink() functionality (file symlinks only)
190:  301bd6ac379 = 172:  28a0713a057 Win32: symlink: add support for symlinks to directories
191:  d46afda2252 = 173:  990995e6031 mingw: try to create symlinks without elevated permissions
192:  1b896e23970 ! 174:  989fd24ef1d mingw: introduce code to detect whether we're inside a Windows container
    @@ Commit message
         Signed-off-by: Johannes Schindelin <[email protected]>
     
      ## compat/mingw.c ##
    -@@ compat/mingw.c: const char *program_data_config(void)
    - 	}
    - 	return *path.buf ? path.buf : NULL;
    +@@ compat/mingw.c: int uname(struct utsname *buf)
    + 		  "%u", (v >> 16) & 0x7fff);
    + 	return 0;
      }
     +
     +/*
    @@ compat/mingw.h
     @@ compat/mingw.h: extern void open_in_gdb(void);
       * Used by Pthread API implementation for Windows
       */
    - extern int err_win_to_posix(DWORD winerr);
    + int err_win_to_posix(DWORD winerr);
     +
     +/*
     + * Check current process is inside Windows Container.
     + */
    -+extern int is_inside_windows_container(void);
    ++int is_inside_windows_container(void);
193:  d65714e421f = 175:  cf3be891367 mingw: when running in a Windows container, try to rename() harder
194:  7f78e3eb29c = 176:  522f8faf1fd mingw: move the file_attr_to_st_mode() function definition
195:  1ce1a71b1eb = 177:  61f12b2a63d mingw: Windows Docker volumes are *not* symbolic links
196:  d50af74908c = 178:  02e71cccb9b Win32: symlink: move phantom symlink creation to a separate function
197:  51540f330c1 ! 179:  9dc0e1fcb4e Introduce helper to create symlinks that knows about index_state
    @@ merge-recursive.c: static int update_file_flags(struct merge_options *opt,
      			safe_create_leading_directories_const(path);
      			unlink(path);
     -			if (symlink(lnk, path))
    -+			if (create_symlink(&opt->orig_index, lnk, path))
    ++			if (create_symlink(&opt->priv->orig_index, lnk, path))
      				ret = err(opt, _("failed to symlink '%s': %s"),
      					  path, strerror(errno));
      			free(lnk);
198:  17618514deb = 180:  adfa76b6f18 mingw: allow to specify the symlink type in .gitattributes
199:  98eda508ebd = 181:  9cb055a3388 Win32: symlink: add test for `symlink` attribute
200:  f3433885642 = 182:  0073f36b109 transport-helper: prefer Git's builtins over dashed form
201:  4df645b15ee = 183:  11e9d1a7ffb mingw: explicitly specify with which cmd to prefix the cmdline
202:  9134b8c1a51 = 184:  4474ff4a105 mingw: when path_lookup() failed, try BusyBox
204:  07fd1644c88 = 185:  ca925a13b1a test-lib: avoid unnecessary Perl invocation
205:  2d193d9c32f = 186:  a38c9650793 tests: replace mingw_test_cmp with a helper in C
206:  5991ad1564f = 187:  38424507807 test-tool: learn to act as a drop-in replacement for `iconv`
207:  070aa66e7b6 = 188:  425fc787f2f tests(mingw): if `iconv` is unavailable, use `test-helper --iconv`
208:  da68ca249af = 189:  a1d48aa4eb0 tests: use t/diff-lib/* consistently
209:  73e76a64e51 = 190:  04e2e5a83f6 gitattributes: mark .png files as binary
210:  f140176722b = 191:  d025e31269d tests: move test PNGs into t/diff-lib/
211:  fc24d93e434 = 192:  8088c8492c9 tests: only override sort & find if there are usable ones in /usr/bin/
212:  d187a6739aa = 193:  f1226a7a8f0 tests: use the correct path separator with BusyBox
213:  89ae6dfabc6 = 194:  ab1c002ef5c mingw: only use Bash-ism `builtin pwd -W` when available
214:  281765f6e9d = 195:  201c2c6f739 tests (mingw): remove Bash-specific pwd option
215:  f89ddab5e89 = 196:  6f6231f0e9b test-lib: add BUSYBOX prerequisite
216:  745b1e3082d = 197:  11cc5c00e8b t0021: use Windows path when appropriate
217:  30be64b6bc6 = 198:  ebf87b556ef t1300: mark all test cases with funny filenames as !MINGW
218:  eff0e00985a = 199:  dd4e12d2f67 t4124: avoid using "normal" diff mode
219:  fafbeb9fdb2 = 200:  8fe29723c5e t5003: use binary file from t/diff-lib/
220:  db2fe014dfa = 201:  261986d2063 t5003: skip `unzip -a` tests with BusyBox
221:  8d8141e12da = 202:  9d0457b436b t5532: workaround for BusyBox on Windows
222:  7567e41f7d3 = 203:  37ce78a35d2 t5605: special-case hardlink test for BusyBox-w32
223:  296036d801c = 204:  57e6a01fb51 t5813: allow for $PWD to be a Windows path
224:  bba9f8041a7 = 205:  2a81e983953 t7063: when running under BusyBox, avoid unsupported find option
225:  2397eb5655f = 206:  0b03eb43abd t9200: skip tests when $PWD contains a colon
227:  05bd9af2cc5 = 207:  68fedcd0d32 mingw: add a Makefile target to copy test artifacts
226:  47242f66fc4 = 208:  318be88faf5 mingw: kill child processes in a gentler way
118:  9902a80fcf1 = 209:  c2df492cc16 status: carry the --no-lock-index option for backwards-compatibility
228:  cfd1e4051e5 = 210:  37f97664c20 mingw: really handle SIGINT
  -:  ----------- > 211:  9520acc3989 Modify the Code of Conduct for Git for Windows
115:  18087d77355 = 212:  30d83601834 CONTRIBUTING.md: add guide for first-time contributors
116:  1ee6d840bea = 213:  14a89fc3ecd README.md: Add a Windows-specific preamble
117:  157095d2e22 = 214:  802db04d6fb Add an issue template
119:  8f9dcd8e3af = 215:  3c3f09b662e Modify the GitHub Pull Request template (to reflect Git for Windows)
121:  5c42e098202 = 216:  4af9fbe8e79 .github: Add configuration for the Sentiment Bot
120:  c1088e2f325 = 217:  855d4dfb969 status: reinstate --show-ignored-directory as a deprecated option
123:  dd1b2ac546e = 218:  267fd7be2fa Document how $HOME is set on Windows
122:  fc45fd3e2b0 = 219:  c8eeace1cfc status: verify that --show-ignored-directory prints a warning
124:  bcbbb4f89f5 <   -:  ----------- Windows: add support for a Windows-wide configuration
159:  7a9af971667 <   -:  ----------- unpack-trees: enable fscache for sparse-checkout
203:  43203eaf6c8 <   -:  ----------- test-run-command: learn to run (parts of) the testsuite
  -:  ----------- > 220:  7350fdf90e1 SECURITY.md: document Git for Windows' policies

Analysis

Let's handle the analysis in parts:

The built-in add -i/add -p patches

- a0b99042966 <   -:  ----------- built-in add -i: refresh the index before running `status`

This was actually a fixup!, really, and was squashed into 008477c ("built-in add -i: implement the status command")

 24:  21c51409f95 <   -:  ----------- built-in add -i: color the header in the `status` command
  -:  ----------- >  17:  cfc303a3e88 built-in add -i: color the header in the `status` command
 26:  235fc8495b3 <   -:  ----------- Add a function to determine unique prefixes for a list of strings
 27:  54039aa229a <   -:  ----------- built-in add -i: show unique prefixes of the commands
  -:  ----------- >  19:  ee39564ee8d built-in add -i: show unique prefixes of the commands
 33:  f4602ba5bf8 <   -:  ----------- built-in add -i: implement the `update` command
  -:  ----------- >  25:  f7b3b9bcaaf built-in add -i: implement the `update` command
 35:  c5fac3487ad <   -:  ----------- built-in add -i: re-implement `add-untracked` in C
  -:  ----------- >  27:  47cd5b58fcf built-in add -i: re-implement `add-untracked` in C
 36:  f22f7b783cf <   -:  ----------- built-in add -i: implement the `patch` command
  -:  ----------- >  28:  6f8d73f0c6b built-in add -i: implement the `patch` command
 37:  040d8e77105 <   -:  ----------- built-in add -i: re-implement the `diff` command
  -:  ----------- >  29:  9bddac0d57d built-in add -i: re-implement the `diff` command
 38:  0efdd4fca2e <   -:  ----------- built-in add -i: offer the `quit` command
  -:  ----------- >  30:  6ba19986d74 built-in add -i: offer the `quit` command
 47:  b650cb9f678 <   -:  ----------- built-in add -i: wire up the new C code for the `patch` command
  -:  ----------- >  39:  f028c857ec7 built-in add -i: wire up the new C code for the `patch` command

These massive changes were all a fall-out from dropping prefix-map.c as suggested by @szeder

 20:  3fcd97e211a !  10:  50f32210c75 Start to implement a built-in version of `git add --interactive`

"builtin" -> "built-in"

 21:  4106c550e59 !  15:  9fbca294e3e diff: export diffstat interface
 22:  457f759265d !  16:  008477c7e10 built-in add -i: implement the `status` command
 25:  5c29435e3d5 !  18:  1e662382825 built-in add -i: implement the main loop
 28:  7c99022bfa4 !  20:  16459c70799 built-in add -i: support `?` (prompt help)
 29:  1160a4fefd5 !  21:  b39de6551ff built-in add -i: use color in the main loop
 30:  34f43346d7d !  22:  add05aef517 built-in add -i: implement the `help` command
 31:  f3b3f0ed911 !  23:  c5b960a0ead built-in add -i: allow filtering the modified files list
 32:  bd5156e9140 !  24:  e8f70db6ad9 built-in add -i: prepare for multi-selection commands
 34:  d3c4b7b7240 !  26:  33ebf58b808 built-in add -i: re-implement `revert` in C
 41:  f417cc7abd7 !  33:  522bb4270fe t3701: add a test for the different `add -p` prompts
 46:  620df4a511f !  38:  f5531786a10 built-in add -i: start implementing the `patch` functionality in C
 48:  ada3381d5ee !  40:  70e601b375e built-in add -p: show colored hunks by default
 49:  5597cae2649 !  41:  a7e0aa80abd built-in add -p: adjust hunk headers as needed
 50:  11baa29113a !  42:  fb22ad91a62 built-in add -p: color the prompt and the help text
 52:  1c31b22a848 !  44:  7647db90299 built-in add -p: support multi-file diffs
 55:  98e70bddc71 !  47:  a0e4df514f0 built-in add -p: show different prompts for mode changes and deletions
 56:  85f0488b311 !  48:  b336dc342d5 built-in add -p: implement the hunk splitting feature
 58:  61a852b5c25 !  50:  991ff42d62a built-in add -p: implement hunk editing
 59:  84e41328770 !  51:  9cd1a973bd2 built-in add -p: implement the 'g' ("goto") command
 60:  59c02506c93 !  52:  5dbae4baa43 built-in add -p: implement the '/' ("search regex") command
 63:  84453baa7b1 !  55:  24df6a1c4e2 built-in add -p: show helpful hint when nothing can be staged
 64:  172dc1ede3d !  56:  8fca5f8ad5e built-in add -p: prepare for patch modes other than "stage"
 66:  01ca0692e71 !  57:  d09568bf856 built-in add -p: implement the "stash" and "reset" patch modes
 65:  5931f6e9067 !  58:  96604247ec9 stash -p: respect the add.interactive.usebuiltin setting
 67:  3c7fe49a382 !  60:  a1706ffa892 built-in add -p: implement the "checkout" patch modes
 71:  50b9fd69852 !  63:  e7bbd99438b built-in add -p: support interactive.diffFilter
 72:  d94657e5612 !  64:  0c950c27175 built-in add -p: handle diff.algorithm
 75:  1c0af6ef090 =  67:  f0df8ce4eb7 terminal: add a new function to read a single keystroke
 76:  1a7378a19be !  68:  068db526a6f built-in add -p: respect the `interactive.singlekey` config setting
 77:  57ee427d9e8 !  69:  4fed9c991e5 built-in add -p: handle Escape sequences in interactive.singlekey mode
 78:  7e8faa0a98e !  70:  d7fa28e0f51 built-in add -p: handle Escape sequences more efficiently

These are mostly another set of fall-outs from the massive work required as part of dropping the prefix map. Other changes include the proper spelling of Slavica's surname, and adjustments to kt/add-i-progress and to tg/stash-refresh-index, as well as fixups addressing #2364.

The "dropped" patches

114:  84404c80afd <   -:  ----------- Add a Code of Conduct
  -:  ----------- > 211:  9520acc3989 Modify the Code of Conduct for Git for Windows

This is actually not a dropped patch, but a modified one: Git now also has a code of conduct, which makes me very happy. It is not a big step, but it is a step in the right direction.

124:  bcbbb4f89f5 <   -:  ----------- Windows: add support for a Windows-wide configuration

This was actually dropped for real, as we reconsolidated the two system gitconfigs into a single one. See #2358 for details.

159:  7a9af971667 <   -:  ----------- unpack-trees: enable fscache for sparse-checkout
  -:  ----------- > 141:  ea8740fb4a5 unpack-trees: enable fscache for sparse-checkout

This is not a dropped patch, but the context changed so much that range-diff did not pick up on it.

The added patches

  -:  ----------- >  76:  8a3a3677f52 gitk: Escape file paths before piping to git log
  -:  ----------- >  83:  058c8faae5e mingw: move Git for Windows' system config where users expect it
  -:  ----------- >  84:  eff1290ede9 vcpkg_install: detect lack of Git
  -:  ----------- >  85:  aa6df6967dd config: normalize the path of the system gitconfig
  -:  ----------- >  87:  c55ee6efeb2 config.mak.uname: PCRE1 cleanup
  -:  ----------- >  88:  3d170b096ee rebase -r: let `label` generate safer labels
  -:  ----------- >  89:  824b7dd44d0 vcpkg_install: add comment regarding slow network connections
  -:  ----------- >  95:  0e2e99d15c2 Config option to disable side-band-64k for transport
  -:  ----------- >  96:  06c0ae22b44 vreportf(): avoid relying on stdio buffering
  -:  ----------- >  97:  aa5ef349fd9 update-index: optionally leave skip-worktree entries alone
  -:  ----------- >  98:  bd7aed58e9a stash: handle staged changes in skip-worktree files correctly
  -:  ----------- > 220:  7350fdf90e1 SECURITY.md: document Git for Windows' policies

These are legitimately new patches that came in via Pull Requests #2297, #2358, #2370, #2351, #2316, #2315, #2375, #2373, #2378, #2303 and #2311.

Happily, #2383 and #2371 and #2359 were already applied upstream, therefore these PRs did not result in added patches.

The modified patches

 98:  5eb3f484ac3 !  78:  61ad94d0b8e clean: do not traverse mount points
164:  d58321c4251 ! 146:  126df7e797f Win32: support long paths

Since the "do not traverse mount points" patch moved before the long-paths patches, it cannot use MAX_LONG_PATH but now needs to use MAX_PATH instead.

Also, due to the dropped support for the ProgramData config, there is now one less site where MAX_PATH needs to be updated to MAX_LONG_PATH by "support long paths".

Note: this change should have been made earlier, when the commit topology was changed, but was forgotten, causing build failures while running git rebase -x "make -j8" HEAD^{/^Start.the.merging.rebase}.

 99:  e23c584977f !  82:  cfaed709844 clean: remove mount points when possible
125:  c318e1f9f6c !  90:  81b74a86626 mingw: demonstrate that all file handles are inherited by child processes
 97:  52b7ae3d676 !  93:  cba298e17c5 Build Python stuff with MSys2
130:  0209b061167 !  94:  a86420d323c mingw: restrict file handle inheritance only on Windows 7 and later
135:  d78df1b9343 ! 115:  a9c8d5005f3 add infrastructure for read-only file system level caches
161:  10f8902b513 ! 143:  713872994a6 fscache: implement an FSCache-aware is_mount_point()
162:  62f1e89ced5 ! 144:  b66730e63ea clean: make use of FSCache
188:  b16097ddd4e ! 170:  3274499e02c Win32: implement readlink()
192:  1b896e23970 ! 174:  989fd24ef1d mingw: introduce code to detect whether we're inside a Windows container

The diff context changed.

136:  f05e7b49af6 ! 117:  09bb1be0aef Win32: add a cache below mingw's lstat and dirent implementations
137:  74a172e9621 ! 119:  d626c4026e5 fscache: load directories only once
139:  72e2cfa017d ! 121:  8343e4518e6 fscache: remember not-found directories
152:  05789f52bc9 ! 134:  5c5fe682ac7 fscache: add fscache hit statistics
155:  9ef4fba9f96 ! 137:  57fab21952f fscache: update fscache to be thread specific instead of global
156:  78291571cbe ! 138:  bd0bc59c41b fscache: teach fscache to use mempool

Fall-out from reaction work caused by ew/hashmap.

142:  56fb329b15a ! 124:  e57967521fa dir.c: make add_excludes aware of fscache during status
143:  f7128a113fe ! 125:  85520e081c8 fscache: make fscache_enabled() public
144:  a812574d49c ! 126:  ad48a7a27c8 dir.c: regression fix for add_excludes with fscache

The context changed (more precisely, the function name), as a consequence of ds/include-exclude.

197:  51540f330c1 ! 179:  9dc0e1fcb4e Introduce helper to create symlinks that knows about index_state

The orig_index field in merge-recursive's data structure was moved to the "private" part, and required this patch update.

@dscho dscho merged commit 6a3fa2a into git-for-windows:master Nov 4, 2019
@dscho dscho deleted the tentative-2.24.0 branch November 4, 2019 09:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.