From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:04
This is the final leg of the journey to a fully built-in git add: the git
add -i and git add -p modes were re-implemented in C, but they lacked
support for a couple of config settings.
The one that sticks out most is the interactive.singleKey setting: it was
particularly hard to get to work, especially on Windows.
It also seems to be the setting that is incomplete already in the Perl
version of the interactive add command: while the name of the config setting
suggests that it applies to all of the interactive add, including the main
loop of git add --interactive and to the file selections in that command, it
does not. Only the git add --patch mode respects that setting.
As it is outside the purpose of the conversion of git-add--interactive.perl
to C, we will leave that loose end for some future date.
Johannes Schindelin (9):
built-in add -p: support interactive.diffFilter
built-in add -p: handle diff.algorithm
terminal: make the code of disable_echo() reusable
terminal: accommodate Git for Windows' default terminal
terminal: add a new function to read a single keystroke
built-in add -p: respect the `interactive.singlekey` config setting
built-in add -p: handle Escape sequences in interactive.singlekey mode
built-in add -p: handle Escape sequences more efficiently
ci: include the built-in `git add -i` in the `linux-gcc` job
add-interactive.c | 19 +++
add-interactive.h | 4 +
add-patch.c | 57 ++++++++-
ci/run-build-and-tests.sh | 1 +
compat/terminal.c | 249 +++++++++++++++++++++++++++++++++++++-
compat/terminal.h | 3 +
6 files changed, 325 insertions(+), 8 deletions(-)
base-commit: 2d4b85ddc76af3e703e6e3a6a72319b5e79c2d8b
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-175%2Fdscho%2Fadd-p-in-c-config-settings-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-175/dscho/add-p-in-c-config-settings-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/175
--
gitgitgadget
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:11
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 12 ++++++++++++
add-interactive.h | 3 +++
add-patch.c | 33 +++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+)
@@ -406,6 +407,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)argv_array_clear(&args);if(res)returnerror(_("could not parse colored diff"));++if(diff_filter){+structchild_processfilter_cp=CHILD_PROCESS_INIT;++setup_child_process(s,&filter_cp,+diff_filter,NULL);+filter_cp.git_cmd=0;+filter_cp.use_shell=1;+strbuf_reset(&s->buf);+if(pipe_command(&filter_cp,+colored->buf,colored->len,+&s->buf,colored->len,+NULL,0)<0)+returnerror(_("failed to run '%s'"),+diff_filter);+strbuf_swap(colored,&s->buf);+}+strbuf_complete_line(colored);colored_p=colored->buf;colored_pend=colored_p+colored->len;
@@ -530,6 +549,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)colored_pend-colored_p);if(colored_eol)colored_p=colored_eol+1;+elseif(p!=pend)+/* colored shorter than non-colored? */+gotomismatched_output;elsecolored_p=colored_pend;
@@ -554,6 +576,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)*/hunk->splittable_into++;+/* non-colored shorter than colored? */+if(colored_p!=colored_pend){+mismatched_output:+error(_("mismatched output from interactive.diffFilter"));+advise(_("Your filter must maintain a one-to-one correspondence\n"+"between its input and output lines."));+return-1;+}+return0;}
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:12
From: Johannes Schindelin <redacted>
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.
Note: just like `git-add--interactive.perl`, we do _not_ respect this
config setting in `git add -i`'s `diff` command, but _only_ in the
`patch` command.
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 5 +++++
add-interactive.h | 2 +-
add-patch.c | 3 +++
3 files changed, 9 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:12
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 2 ++
add-interactive.h | 1 +
add-patch.c | 21 +++++++++++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:12
From: Johannes Schindelin <redacted>
When `interactive.singlekey = true`, we react immediately to keystrokes,
even to Escape sequences (e.g. when pressing a cursor key).
The problem with Escape sequences is that we do not really know when
they are done, and as a heuristic we poll standard input for half a
second to make sure that we got all of it.
While waiting half a second is not asking for a whole lot, it can become
quite annoying over time, therefore with this patch, we read the
terminal capabilities (if available) and extract known Escape sequences
from there, then stop polling immediately when we detected that the user
pressed a key that generated such a known sequence.
This recapitulates the remaining part of b5cc003253c8 (add -i: ignore
terminal escape sequences, 2011-05-17).
Note: We do *not* query the terminal capabilities directly. That would
either require a lot of platform-specific code, or it would require
linking to a library such as ncurses.
Linking to a library in the built-ins is something we try very hard to
avoid (we even kicked the libcurl dependency to a non-built-in remote
helper, just to shave off a tiny fraction of a second from Git's startup
time). And the platform-specific code would be a maintenance nightmare.
Even worse: in Git for Windows' case, we would need to query MSYS2
pseudo terminals, which `git.exe` simply cannot do (because it is
intentionally *not* an MSYS2 program).
To address this, we simply spawn `infocmp -L -1` and parse its output
(which works even in Git for Windows, because that helper is included in
the end-user facing installations).
This is done only once, as in the Perl version, but it is done only when
the first Escape sequence is encountered, not upon startup of `git add
-i`; This saves on startup time, yet makes reacting to the first Escape
sequence slightly more sluggish. But it allows us to keep the
terminal-related code encapsulated in the `compat/terminal.c` file.
Signed-off-by: Johannes Schindelin <redacted>
---
compat/terminal.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:12
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
@@ -79,6 +98,37 @@ static void restore_term(void)staticintdisable_bits(DWORDbits){+if(use_stty){+structchild_processcp=CHILD_PROCESS_INIT;++argv_array_push(&cp.args,"stty");++if(bits&ENABLE_LINE_INPUT){+string_list_append(&stty_restore,"icanon");+argv_array_push(&cp.args,"-icanon");+}++if(bits&ENABLE_ECHO_INPUT){+string_list_append(&stty_restore,"echo");+argv_array_push(&cp.args,"-echo");+}++if(bits&ENABLE_PROCESSED_INPUT){+string_list_append(&stty_restore,"-ignbrk");+string_list_append(&stty_restore,"intr");+string_list_append(&stty_restore,"^c");+argv_array_push(&cp.args,"ignbrk");+argv_array_push(&cp.args,"intr");+argv_array_push(&cp.args,"");+}++if(run_command(&cp)==0)+return0;++/* `stty` could not be executed; access the Console directly */+use_stty=0;+}+hconin=CreateFile("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:13
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:14
From: Johannes Schindelin <redacted>
This recapitulates part of b5cc003253c8 (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 <redacted>
---
compat/terminal.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:14
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++
compat/terminal.h | 3 +++
2 files changed, 58 insertions(+)
@@ -60,6 +60,11 @@ static int disable_echo(void)returndisable_bits(ECHO);}+staticintenable_non_canonical(void)+{+returndisable_bits(ICANON|ECHO);+}+#elif defined(GIT_WINDOWS_NATIVE)#define INPUT_PATH "CONIN$"
@@ -151,6 +156,10 @@ static int disable_echo(void)returndisable_bits(ENABLE_ECHO_INPUT);}+staticintenable_non_canonical(void)+{+returndisable_bits(ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT);+}#endif
@@ -198,6 +207,33 @@ char *git_terminal_prompt(const char *prompt, int echo)returnbuf.buf;}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+intch;++if(warning_displayed||enable_non_canonical()<0){+if(!warning_displayed){+warning("reading single keystrokes not supported on "+"this platform; reading line instead");+warning_displayed=1;+}++returnstrbuf_getline(buf,stdin);+}++strbuf_reset(buf);+ch=getchar();+if(ch==EOF){+restore_term();+returnEOF;+}++strbuf_addch(buf,ch);+restore_term();+return0;+}+#elsechar*git_terminal_prompt(constchar*prompt,intecho)
@@ -205,4 +241,23 @@ char *git_terminal_prompt(const char *prompt, int echo)returngetpass(prompt);}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+constchar*res;++if(!warning_displayed){+warning("reading single keystrokes not supported on this "+"platform; reading line instead");+warning_displayed=1;+}++res=getpass("");+strbuf_reset(buf);+if(!res)+returnEOF;+strbuf_addstr(buf,res);+return0;+}+#endif
@@ -3,4 +3,7 @@char*git_terminal_prompt(constchar*prompt,intecho);+/* Read a single keystroke, without echoing it to the terminal */+intread_key_without_echo(structstrbuf*buf);+#endif /* COMPAT_TERMINAL_H */
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-21 22:42:17
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_MULTI_PACK_INDEX` into that
fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
From: SZEDER Gábor <hidden> Date: 2019-12-21 22:54:11
On Sat, Dec 21, 2019 at 10:42:00PM +0000, Johannes Schindelin via GitGitGadget wrote:
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_MULTI_PACK_INDEX` into that
From: Johannes Schindelin <hidden> Date: 2019-12-25 11:56:38
Hi Gábor,
On Sat, 21 Dec 2019, SZEDER Gábor wrote:
On Sat, Dec 21, 2019 at 10:42:00PM +0000, Johannes Schindelin via GitGitGadget wrote:
quoted
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_MULTI_PACK_INDEX` into that
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:06
This is the final leg of the journey to a fully built-in git add: the git
add -i and git add -p modes were re-implemented in C, but they lacked
support for a couple of config settings.
The one that sticks out most is the interactive.singleKey setting: it was
particularly hard to get to work, especially on Windows.
It also seems to be the setting that is incomplete already in the Perl
version of the interactive add command: while the name of the config setting
suggests that it applies to all of the interactive add, including the main
loop of git add --interactive and to the file selections in that command, it
does not. Only the git add --patch mode respects that setting.
As it is outside the purpose of the conversion of git-add--interactive.perl
to C, we will leave that loose end for some future date.
Changes since v1:
* Fixed the commit message where a copy/paste fail made it talk about
another GIT_TEST_* variable than the GIT_TEST_ADD_I_USE_BUILTIN one.
Johannes Schindelin (9):
built-in add -p: support interactive.diffFilter
built-in add -p: handle diff.algorithm
terminal: make the code of disable_echo() reusable
terminal: accommodate Git for Windows' default terminal
terminal: add a new function to read a single keystroke
built-in add -p: respect the `interactive.singlekey` config setting
built-in add -p: handle Escape sequences in interactive.singlekey mode
built-in add -p: handle Escape sequences more efficiently
ci: include the built-in `git add -i` in the `linux-gcc` job
add-interactive.c | 19 +++
add-interactive.h | 4 +
add-patch.c | 57 ++++++++-
ci/run-build-and-tests.sh | 1 +
compat/terminal.c | 249 +++++++++++++++++++++++++++++++++++++-
compat/terminal.h | 3 +
6 files changed, 325 insertions(+), 8 deletions(-)
base-commit: c480eeb574e649a19f27dc09a994e45f9b2c2622
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-175%2Fdscho%2Fadd-p-in-c-config-settings-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-175/dscho/add-p-in-c-config-settings-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/175
Range-diff vs v1:
1: a7355776d6 = 1: f45ff08bd0 built-in add -p: support interactive.diffFilter
2: 74958419f6 ! 2: e9c4a13cbf built-in add -p: handle diff.algorithm
@@ -62,7 +62,7 @@
@@
int res;
- argv_array_pushv(&args, s->mode->diff);
+ argv_array_pushv(&args, s->mode->diff_cmd);
+ if (diff_algorithm)
+ argv_array_pushf(&args, "--diff-algorithm=%s", diff_algorithm);
if (s->revision) {
3: 7631c1ea8c = 3: e643554dba terminal: make the code of disable_echo() reusable
4: a77fa914da = 4: bd2306c5d5 terminal: accommodate Git for Windows' default terminal
5: 3996d7997a = 5: 190fb4f5e9 terminal: add a new function to read a single keystroke
6: 6d6794089d ! 6: 167dfa37dd built-in add -p: respect the `interactive.singlekey` config setting
@@ -54,7 +54,7 @@
+#include "compat/terminal.h"
enum prompt_mode_type {
- PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
+ PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK,
@@
return 0;
}
7: fd5a129776 = 7: 32067bebe8 built-in add -p: handle Escape sequences in interactive.singlekey mode
8: af9b598738 = 8: 703719ffce built-in add -p: handle Escape sequences more efficiently
9: 9719604a1f ! 9: 23a3a47b01 ci: include the built-in `git add -i` in the `linux-gcc` job
@@ -6,8 +6,8 @@
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
- feature-complete, let's also throw `GIT_TEST_MULTI_PACK_INDEX` into that
- fray.
+ feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
+ that fray.
Signed-off-by: Johannes Schindelin [off-list ref]
--
gitgitgadget
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:07
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 12 ++++++++++++
add-interactive.h | 3 +++
add-patch.c | 33 +++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+)
@@ -407,6 +408,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)argv_array_clear(&args);if(res)returnerror(_("could not parse colored diff"));++if(diff_filter){+structchild_processfilter_cp=CHILD_PROCESS_INIT;++setup_child_process(s,&filter_cp,+diff_filter,NULL);+filter_cp.git_cmd=0;+filter_cp.use_shell=1;+strbuf_reset(&s->buf);+if(pipe_command(&filter_cp,+colored->buf,colored->len,+&s->buf,colored->len,+NULL,0)<0)+returnerror(_("failed to run '%s'"),+diff_filter);+strbuf_swap(colored,&s->buf);+}+strbuf_complete_line(colored);colored_p=colored->buf;colored_pend=colored_p+colored->len;
@@ -531,6 +550,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)colored_pend-colored_p);if(colored_eol)colored_p=colored_eol+1;+elseif(p!=pend)+/* colored shorter than non-colored? */+gotomismatched_output;elsecolored_p=colored_pend;
@@ -555,6 +577,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)*/hunk->splittable_into++;+/* non-colored shorter than colored? */+if(colored_p!=colored_pend){+mismatched_output:+error(_("mismatched output from interactive.diffFilter"));+advise(_("Your filter must maintain a one-to-one correspondence\n"+"between its input and output lines."));+return-1;+}+return0;}
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:07
From: Johannes Schindelin <redacted>
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.
Note: just like `git-add--interactive.perl`, we do _not_ respect this
config setting in `git add -i`'s `diff` command, but _only_ in the
`patch` command.
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 5 +++++
add-interactive.h | 2 +-
add-patch.c | 3 +++
3 files changed, 9 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:10
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:11
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
@@ -79,6 +98,37 @@ static void restore_term(void)staticintdisable_bits(DWORDbits){+if(use_stty){+structchild_processcp=CHILD_PROCESS_INIT;++argv_array_push(&cp.args,"stty");++if(bits&ENABLE_LINE_INPUT){+string_list_append(&stty_restore,"icanon");+argv_array_push(&cp.args,"-icanon");+}++if(bits&ENABLE_ECHO_INPUT){+string_list_append(&stty_restore,"echo");+argv_array_push(&cp.args,"-echo");+}++if(bits&ENABLE_PROCESSED_INPUT){+string_list_append(&stty_restore,"-ignbrk");+string_list_append(&stty_restore,"intr");+string_list_append(&stty_restore,"^c");+argv_array_push(&cp.args,"ignbrk");+argv_array_push(&cp.args,"intr");+argv_array_push(&cp.args,"");+}++if(run_command(&cp)==0)+return0;++/* `stty` could not be executed; access the Console directly */+use_stty=0;+}+hconin=CreateFile("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:12
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 2 ++
add-interactive.h | 1 +
add-patch.c | 21 +++++++++++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:12
From: Johannes Schindelin <redacted>
When `interactive.singlekey = true`, we react immediately to keystrokes,
even to Escape sequences (e.g. when pressing a cursor key).
The problem with Escape sequences is that we do not really know when
they are done, and as a heuristic we poll standard input for half a
second to make sure that we got all of it.
While waiting half a second is not asking for a whole lot, it can become
quite annoying over time, therefore with this patch, we read the
terminal capabilities (if available) and extract known Escape sequences
from there, then stop polling immediately when we detected that the user
pressed a key that generated such a known sequence.
This recapitulates the remaining part of b5cc003253c8 (add -i: ignore
terminal escape sequences, 2011-05-17).
Note: We do *not* query the terminal capabilities directly. That would
either require a lot of platform-specific code, or it would require
linking to a library such as ncurses.
Linking to a library in the built-ins is something we try very hard to
avoid (we even kicked the libcurl dependency to a non-built-in remote
helper, just to shave off a tiny fraction of a second from Git's startup
time). And the platform-specific code would be a maintenance nightmare.
Even worse: in Git for Windows' case, we would need to query MSYS2
pseudo terminals, which `git.exe` simply cannot do (because it is
intentionally *not* an MSYS2 program).
To address this, we simply spawn `infocmp -L -1` and parse its output
(which works even in Git for Windows, because that helper is included in
the end-user facing installations).
This is done only once, as in the Perl version, but it is done only when
the first Escape sequence is encountered, not upon startup of `git add
-i`; This saves on startup time, yet makes reacting to the first Escape
sequence slightly more sluggish. But it allows us to keep the
terminal-related code encapsulated in the `compat/terminal.c` file.
Signed-off-by: Johannes Schindelin <redacted>
---
compat/terminal.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:14
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
that fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:16
From: Johannes Schindelin <redacted>
This recapitulates part of b5cc003253c8 (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 <redacted>
---
compat/terminal.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2019-12-25 11:57:17
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++
compat/terminal.h | 3 +++
2 files changed, 58 insertions(+)
@@ -60,6 +60,11 @@ static int disable_echo(void)returndisable_bits(ECHO);}+staticintenable_non_canonical(void)+{+returndisable_bits(ICANON|ECHO);+}+#elif defined(GIT_WINDOWS_NATIVE)#define INPUT_PATH "CONIN$"
@@ -151,6 +156,10 @@ static int disable_echo(void)returndisable_bits(ENABLE_ECHO_INPUT);}+staticintenable_non_canonical(void)+{+returndisable_bits(ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT);+}#endif
@@ -198,6 +207,33 @@ char *git_terminal_prompt(const char *prompt, int echo)returnbuf.buf;}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+intch;++if(warning_displayed||enable_non_canonical()<0){+if(!warning_displayed){+warning("reading single keystrokes not supported on "+"this platform; reading line instead");+warning_displayed=1;+}++returnstrbuf_getline(buf,stdin);+}++strbuf_reset(buf);+ch=getchar();+if(ch==EOF){+restore_term();+returnEOF;+}++strbuf_addch(buf,ch);+restore_term();+return0;+}+#elsechar*git_terminal_prompt(constchar*prompt,intecho)
@@ -205,4 +241,23 @@ char *git_terminal_prompt(const char *prompt, int echo)returngetpass(prompt);}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+constchar*res;++if(!warning_displayed){+warning("reading single keystrokes not supported on this "+"platform; reading line instead");+warning_displayed=1;+}++res=getpass("");+strbuf_reset(buf);+if(!res)+returnEOF;+strbuf_addstr(buf,res);+return0;+}+#endif
@@ -3,4 +3,7 @@char*git_terminal_prompt(constchar*prompt,intecho);+/* Read a single keystroke, without echoing it to the terminal */+intread_key_without_echo(structstrbuf*buf);+#endif /* COMPAT_TERMINAL_H */
On 12/25/2019 6:57 AM, Johannes Schindelin via GitGitGadget wrote:
quoted hunk
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
that fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
From: Johannes Schindelin <hidden> Date: 2020-01-01 22:10:55
Hi Stolee,
On Thu, 26 Dec 2019, Derrick Stolee wrote:
On 12/25/2019 6:57 AM, Johannes Schindelin via GitGitGadget wrote:
quoted
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
that fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
I see that I need to add this to the test-coverage builds.
Thank you for catching this!
It makes me wonder whether the test-coverage builds should use some `sed`
invocation on the `ci/run-build-and-tests.sh` script, though, so that you
do not have to edit the Azure Pipelines definition manually all the time?
Ciao,
Dscho
From: SZEDER Gábor <hidden> Date: 2020-01-07 22:57:56
On Wed, Dec 25, 2019 at 11:56:52AM +0000, Johannes Schindelin via GitGitGadget wrote:
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.
So this patch makes the test 'detect bogus diffFilter output' in
't3701-add-interactive.sh' succeed with the builtin interactive add,
but I stumbled upon a test failure caused by SIGPIPE in an
experimental Travis CI s390x build:
expecting success of 3701.49 'detect bogus diffFilter output':
git reset --hard &&
echo content >test &&
test_config interactive.diffFilter "echo too-short" &&
printf y >y &&
test_must_fail force_color git add -p <y
+ git reset --hard
HEAD is now at 6ee5ee5 test
+ echo content
+ test_config interactive.diffFilter echo too-short
+ printf y
+ test_must_fail force_color git add -p
test_must_fail: died by signal 13: force_color git add -p
error: last command exited with $?=1
Turns out it's a general issue, and
GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49 --stress
fails within 10 seconds on my Linux box, whereas the scripted 'add -p'
managed to survive a couple hundred repetitions.
@@ -407,6 +408,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)argv_array_clear(&args);if(res)returnerror(_("could not parse colored diff"));++if(diff_filter){+structchild_processfilter_cp=CHILD_PROCESS_INIT;++setup_child_process(s,&filter_cp,+diff_filter,NULL);+filter_cp.git_cmd=0;+filter_cp.use_shell=1;+strbuf_reset(&s->buf);+if(pipe_command(&filter_cp,+colored->buf,colored->len,+&s->buf,colored->len,+NULL,0)<0)+returnerror(_("failed to run '%s'"),+diff_filter);+strbuf_swap(colored,&s->buf);+}+strbuf_complete_line(colored);colored_p=colored->buf;colored_pend=colored_p+colored->len;
@@ -531,6 +550,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)colored_pend-colored_p);if(colored_eol)colored_p=colored_eol+1;+elseif(p!=pend)+/* colored shorter than non-colored? */+gotomismatched_output;elsecolored_p=colored_pend;
@@ -555,6 +577,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)*/hunk->splittable_into++;+/* non-colored shorter than colored? */+if(colored_p!=colored_pend){+mismatched_output:+error(_("mismatched output from interactive.diffFilter"));+advise(_("Your filter must maintain a one-to-one correspondence\n"+"between its input and output lines."));+return-1;+}+return0;}
From: Johannes Schindelin <hidden> Date: 2020-01-13 06:47:32
Hi Gábor,
On Tue, 7 Jan 2020, SZEDER Gábor wrote:
On Wed, Dec 25, 2019 at 11:56:52AM +0000, Johannes Schindelin via GitGitGadget wrote:
quoted
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.
So this patch makes the test 'detect bogus diffFilter output' in
't3701-add-interactive.sh' succeed with the builtin interactive add,
but I stumbled upon a test failure caused by SIGPIPE in an
experimental Travis CI s390x build:
expecting success of 3701.49 'detect bogus diffFilter output':
git reset --hard &&
echo content >test &&
test_config interactive.diffFilter "echo too-short" &&
printf y >y &&
test_must_fail force_color git add -p <y
+ git reset --hard
HEAD is now at 6ee5ee5 test
+ echo content
+ test_config interactive.diffFilter echo too-short
+ printf y
+ test_must_fail force_color git add -p
test_must_fail: died by signal 13: force_color git add -p
error: last command exited with $?=1
Turns out it's a general issue, and
GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49 --stress
fails within 10 seconds on my Linux box, whereas the scripted 'add -p'
managed to survive a couple hundred repetitions.
You're right, of course. And I had let that slip for too long, as I saw it
sporadically happen in the Azure Pipeline, too.
This took quite a while to figure out, and I won't claim that I understand
_all_ the details: I _think_ that `stdin` being so short "breaks the pipe"
and interferes with `add -p`'s normal operation, so I needed to explicitly
use the `sigchain` feature to ignore `SIGPIPE` during `add -p`'s main
loop.
Thanks,
Dscho
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:36
From: Johannes Schindelin <redacted>
As noticed by Gábor Szeder, if we want to run `git add -p` with
redirected input through `test_must_fail` in the test suite, we must
expect that a SIGPIPE can happen due to `stdin` coming to its end.
The appropriate action here is to ignore that signal and treat it as a
regular end-of-file, otherwise the test will fail. In preparation for
such a test, introduce precisely this handling of SIGPIPE into the
built-in version of `git add -p`.
For good measure, teach the built-in `git add -i` the same trick: it
_also_ runs a loop waiting for input, and can receive a SIGPIPE just the
same (and wants to treat it as end-of-file, too).
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 3 +++
add-patch.c | 4 ++++
2 files changed, 7 insertions(+)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:37
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 12 ++++++++++++
add-interactive.h | 3 +++
add-patch.c | 33 +++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+)
@@ -408,6 +409,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)argv_array_clear(&args);if(res)returnerror(_("could not parse colored diff"));++if(diff_filter){+structchild_processfilter_cp=CHILD_PROCESS_INIT;++setup_child_process(s,&filter_cp,+diff_filter,NULL);+filter_cp.git_cmd=0;+filter_cp.use_shell=1;+strbuf_reset(&s->buf);+if(pipe_command(&filter_cp,+colored->buf,colored->len,+&s->buf,colored->len,+NULL,0)<0)+returnerror(_("failed to run '%s'"),+diff_filter);+strbuf_swap(colored,&s->buf);+}+strbuf_complete_line(colored);colored_p=colored->buf;colored_pend=colored_p+colored->len;
@@ -532,6 +551,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)colored_pend-colored_p);if(colored_eol)colored_p=colored_eol+1;+elseif(p!=pend)+/* colored shorter than non-colored? */+gotomismatched_output;elsecolored_p=colored_pend;
@@ -556,6 +578,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)*/hunk->splittable_into++;+/* non-colored shorter than colored? */+if(colored_p!=colored_pend){+mismatched_output:+error(_("mismatched output from interactive.diffFilter"));+advise(_("Your filter must maintain a one-to-one correspondence\n"+"between its input and output lines."));+return-1;+}+return0;}
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:38
From: Johannes Schindelin <redacted>
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.
Note: just like `git-add--interactive.perl`, we do _not_ respect this
config setting in `git add -i`'s `diff` command, but _only_ in the
`patch` command.
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 5 +++++
add-interactive.h | 2 +-
add-patch.c | 3 +++
3 files changed, 9 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:39
This is the final leg of the journey to a fully built-in git add: the git
add -i and git add -p modes were re-implemented in C, but they lacked
support for a couple of config settings.
The one that sticks out most is the interactive.singleKey setting: it was
particularly hard to get to work, especially on Windows.
It also seems to be the setting that is incomplete already in the Perl
version of the interactive add command: while the name of the config setting
suggests that it applies to all of the interactive add, including the main
loop of git add --interactive and to the file selections in that command, it
does not. Only the git add --patch mode respects that setting.
As it is outside the purpose of the conversion of git-add--interactive.perl
to C, we will leave that loose end for some future date.
Changes since v2:
* Fixed the SIGPIPE issue pointed out by Gábor Szeder.
Changes since v1:
* Fixed the commit message where a copy/paste fail made it talk about
another GIT_TEST_* variable than the GIT_TEST_ADD_I_USE_BUILTIN one.
Johannes Schindelin (10):
built-in add -i/-p: treat SIGPIPE as EOF
built-in add -p: support interactive.diffFilter
built-in add -p: handle diff.algorithm
terminal: make the code of disable_echo() reusable
terminal: accommodate Git for Windows' default terminal
terminal: add a new function to read a single keystroke
built-in add -p: respect the `interactive.singlekey` config setting
built-in add -p: handle Escape sequences in interactive.singlekey mode
built-in add -p: handle Escape sequences more efficiently
ci: include the built-in `git add -i` in the `linux-gcc` job
add-interactive.c | 22 ++++
add-interactive.h | 4 +
add-patch.c | 61 +++++++++-
ci/run-build-and-tests.sh | 1 +
compat/terminal.c | 249 +++++++++++++++++++++++++++++++++++++-
compat/terminal.h | 3 +
6 files changed, 332 insertions(+), 8 deletions(-)
base-commit: c480eeb574e649a19f27dc09a994e45f9b2c2622
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-175%2Fdscho%2Fadd-p-in-c-config-settings-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-175/dscho/add-p-in-c-config-settings-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/175
Range-diff vs v2:
-: ---------- > 1: 5e258a8d2b built-in add -i/-p: treat SIGPIPE as EOF
1: f45ff08bd0 ! 2: 2a5951ecfe built-in add -p: support interactive.diffFilter
@@ -35,9 +35,9 @@
strbuf_release(&header);
prefix_item_list_clear(&commands);
+ clear_add_i_state(&s);
+ sigchain_pop(SIGPIPE);
return res;
- }
diff --git a/add-interactive.h b/add-interactive.h
--- a/add-interactive.h
@@ -123,13 +123,14 @@
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
+ sigchain_pop(SIGPIPE);
return -1;
}
-
@@
strbuf_release(&s.buf);
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
+ sigchain_pop(SIGPIPE);
return 0;
}
2: e9c4a13cbf = 3: a2bce01818 built-in add -p: handle diff.algorithm
3: e643554dba = 4: be40a37c0c terminal: make the code of disable_echo() reusable
4: bd2306c5d5 = 5: 233f23791c terminal: accommodate Git for Windows' default terminal
5: 190fb4f5e9 = 6: 74593b5115 terminal: add a new function to read a single keystroke
6: 167dfa37dd ! 7: 197fe1e14a built-in add -p: respect the `interactive.singlekey` config setting
@@ -48,9 +48,9 @@
--- a/add-patch.c
+++ b/add-patch.c
@@
- #include "pathspec.h"
#include "color.h"
#include "diff.h"
+ #include "sigchain.h"
+#include "compat/terminal.h"
enum prompt_mode_type {
7: 32067bebe8 = 8: 9ab381d539 built-in add -p: handle Escape sequences in interactive.singlekey mode
8: 703719ffce = 9: bdb6268b8b built-in add -p: handle Escape sequences more efficiently
9: 23a3a47b01 = 10: c4195969a6 ci: include the built-in `git add -i` in the `linux-gcc` job
--
gitgitgadget
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:40
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
@@ -79,6 +98,37 @@ static void restore_term(void)staticintdisable_bits(DWORDbits){+if(use_stty){+structchild_processcp=CHILD_PROCESS_INIT;++argv_array_push(&cp.args,"stty");++if(bits&ENABLE_LINE_INPUT){+string_list_append(&stty_restore,"icanon");+argv_array_push(&cp.args,"-icanon");+}++if(bits&ENABLE_ECHO_INPUT){+string_list_append(&stty_restore,"echo");+argv_array_push(&cp.args,"-echo");+}++if(bits&ENABLE_PROCESSED_INPUT){+string_list_append(&stty_restore,"-ignbrk");+string_list_append(&stty_restore,"intr");+string_list_append(&stty_restore,"^c");+argv_array_push(&cp.args,"ignbrk");+argv_array_push(&cp.args,"intr");+argv_array_push(&cp.args,"");+}++if(run_command(&cp)==0)+return0;++/* `stty` could not be executed; access the Console directly */+use_stty=0;+}+hconin=CreateFile("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:42
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:43
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++
compat/terminal.h | 3 +++
2 files changed, 58 insertions(+)
@@ -60,6 +60,11 @@ static int disable_echo(void)returndisable_bits(ECHO);}+staticintenable_non_canonical(void)+{+returndisable_bits(ICANON|ECHO);+}+#elif defined(GIT_WINDOWS_NATIVE)#define INPUT_PATH "CONIN$"
@@ -151,6 +156,10 @@ static int disable_echo(void)returndisable_bits(ENABLE_ECHO_INPUT);}+staticintenable_non_canonical(void)+{+returndisable_bits(ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT);+}#endif
@@ -198,6 +207,33 @@ char *git_terminal_prompt(const char *prompt, int echo)returnbuf.buf;}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+intch;++if(warning_displayed||enable_non_canonical()<0){+if(!warning_displayed){+warning("reading single keystrokes not supported on "+"this platform; reading line instead");+warning_displayed=1;+}++returnstrbuf_getline(buf,stdin);+}++strbuf_reset(buf);+ch=getchar();+if(ch==EOF){+restore_term();+returnEOF;+}++strbuf_addch(buf,ch);+restore_term();+return0;+}+#elsechar*git_terminal_prompt(constchar*prompt,intecho)
@@ -205,4 +241,23 @@ char *git_terminal_prompt(const char *prompt, int echo)returngetpass(prompt);}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+constchar*res;++if(!warning_displayed){+warning("reading single keystrokes not supported on this "+"platform; reading line instead");+warning_displayed=1;+}++res=getpass("");+strbuf_reset(buf);+if(!res)+returnEOF;+strbuf_addstr(buf,res);+return0;+}+#endif
@@ -3,4 +3,7 @@char*git_terminal_prompt(constchar*prompt,intecho);+/* Read a single keystroke, without echoing it to the terminal */+intread_key_without_echo(structstrbuf*buf);+#endif /* COMPAT_TERMINAL_H */
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:45
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 2 ++
add-interactive.h | 1 +
add-patch.c | 21 +++++++++++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:46
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
that fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:47
From: Johannes Schindelin <redacted>
When `interactive.singlekey = true`, we react immediately to keystrokes,
even to Escape sequences (e.g. when pressing a cursor key).
The problem with Escape sequences is that we do not really know when
they are done, and as a heuristic we poll standard input for half a
second to make sure that we got all of it.
While waiting half a second is not asking for a whole lot, it can become
quite annoying over time, therefore with this patch, we read the
terminal capabilities (if available) and extract known Escape sequences
from there, then stop polling immediately when we detected that the user
pressed a key that generated such a known sequence.
This recapitulates the remaining part of b5cc003253c8 (add -i: ignore
terminal escape sequences, 2011-05-17).
Note: We do *not* query the terminal capabilities directly. That would
either require a lot of platform-specific code, or it would require
linking to a library such as ncurses.
Linking to a library in the built-ins is something we try very hard to
avoid (we even kicked the libcurl dependency to a non-built-in remote
helper, just to shave off a tiny fraction of a second from Git's startup
time). And the platform-specific code would be a maintenance nightmare.
Even worse: in Git for Windows' case, we would need to query MSYS2
pseudo terminals, which `git.exe` simply cannot do (because it is
intentionally *not* an MSYS2 program).
To address this, we simply spawn `infocmp -L -1` and parse its output
(which works even in Git for Windows, because that helper is included in
the end-user facing installations).
This is done only once, as in the Perl version, but it is done only when
the first Escape sequence is encountered, not upon startup of `git add
-i`; This saves on startup time, yet makes reacting to the first Escape
sequence slightly more sluggish. But it allows us to keep the
terminal-related code encapsulated in the `compat/terminal.c` file.
Signed-off-by: Johannes Schindelin <redacted>
---
compat/terminal.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-13 08:29:48
From: Johannes Schindelin <redacted>
This recapitulates part of b5cc003253c8 (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 <redacted>
---
compat/terminal.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
From: SZEDER Gábor <hidden> Date: 2020-01-13 17:04:25
On Mon, Jan 13, 2020 at 08:29:22AM +0000, Johannes Schindelin via GitGitGadget wrote:
From: Johannes Schindelin <redacted>
As noticed by Gábor Szeder, if we want to run `git add -p` with
redirected input through `test_must_fail` in the test suite, we must
expect that a SIGPIPE can happen due to `stdin` coming to its end.
I don't think this issue is related to the redirected input: I
modified that flaky test to send "unlimited" data to 'git add's stdin,
i.e.:
/usr/bin/yes | test_must_fail force_color git add -p
and the test with --stress still failed with SIGPIPE all the same and
just as fast.
After looking into it, the issue seems to be sending data to the
broken diffFilter process. So in that test the diff is "filtered"
through 'echo too-short', which exits real fast, and doesn't read its
standard input at all (well, apart from e.g. the usual kernel
buffering that might happen on a pipe between the two processes).
Making sure that the diffFilter process reads all the data before
exiting, i.e. changing it to:
test_config interactive.diffFilter "cat >/dev/null ; echo too-short" &&
made the test reliable, with over 2000 --stress repetitions, and that
with only a single "y" on 'git add's stdin.
Now, merely tweaking the test is clearly insufficient, because we not
only want the test to be realiable, but we want 'git add' to die
gracefully when users out there mess up their configuration.
Ignoring SIGPIPE can surely accomplish that, but I'm not sure about
the scope. I mean your patch seems to ignore SIGPIPE basically for
almost the whole 'git add -(i|p)' process, but perhaps it should be
limited only to the surroundings of the pipe_command() call running
the diffFilter, and be done as part of the next patch adding the 'if
(diff_filter)' block.
Furthermore, I'm worried that by simply ignoring SIGPIPE we might just
ignore a more fundamental issue in pipe_command(): shouldn't that
function be smart enough not to write() to a fd that has no one on the
other side to read it in the first place?!
So, when the diffFilter process exits unexpectedly early, then the
poll() call in pipe_command() -> pump_io() -> pump_io_round() returns
with success and usually sets 'revents' for the child process' stdin
to 12 (i.e. 'POLLOUT | POLLERR'; gah, how I hate unnamed constants :).
Unfortunately, at that point we don't take any special action on
POLLERR, but call xwrite() to try to write to the dead fd anyway,
which then promptly triggers SIGPIPE. (This is what usually happens
when stepping through the statements of those functions in a debugger,
and the diffFilter process has all the time in the world to exit.)
We could handle POLLERR with a patch like this:
--- >8 ---
Subject: run-command: handle POLLERR in pump_io_round() to reduce risk of SIGPIPE
@@ -1416,25 +1416,31 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)if(poll(pfd,pollsize,-1)<0){if(errno==EINTR)return1;die_errno("poll failed");}for(i=0;i<nr;i++){structio_pump*io=&slots[i];if(io->fd<0)continue;-if(!(io->pfd->revents&(POLLOUT|POLLIN|POLLHUP|POLLERR|POLLNVAL)))+if(io->pfd->revents&POLLERR){+io->error=ECONNRESET;/* What should we report to the caller? */+close(io->fd);+io->fd=-1;+continue;+}+if(!(io->pfd->revents&(POLLOUT|POLLIN|POLLHUP|POLLNVAL)))continue;if(io->type==POLLOUT){ssize_tlen=xwrite(io->fd,io->u.out.buf,io->u.out.len);if(len<0){io->error=errno;close(io->fd);io->fd=-1;}else{io->u.out.buf+=len;io->u.out.len-=len;--->8---
Unfortunately #1, this changes the error 'git add -p' dies with from:
error: mismatched output from interactive.diffFilter
to:
error: failed to run 'echo too-short'
It might affect other commands as well, but FWIW the test suite
doesn't catch any.
Unfortunately #2, the above patch doesn't completely eliminates the
SIGPIPE, but only (greatly) reduces its probability. It is possible
that:
- poll() returns with success and indicating a writable fd without
any error, i.e. 'revents = 4'.
- the bogus diffFilter exits, closing its stdin.
- 'git add' attempts to xwrite() to the now closed fd, and triggers
a SIGPIPE right away.
This happens much rarer, 'GIT_TEST_ADD_I_USE_BUILTIN=1
./t3701-add-interactive.sh -r 39,49 --stress-jobs=<4*nr-of-cores>
--stress' tends to take over 200 repetitions. The patch below
reproduces it fairly reliably by adding two strategically-placed
sleep()s, with a bit of extra debug output:
--- >8 ---
@@ -1419,6 +1419,7 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)die_errno("poll failed");}+sleep(2);for(i=0;i<nr;i++){structio_pump*io=&slots[i];
@@ -1435,8 +1436,11 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)continue;if(io->type==POLLOUT){-ssize_tlen=xwrite(io->fd,+ssize_tlen;+fprintf(stderr,"attempting to xwrite() %lu bytes to a fd with revents flags 0x%hx\n",io->u.out.len,io->pfd->revents);+len=xwrite(io->fd,io->u.out.buf,io->u.out.len);+fprintf(stderr,"after xwrite()\n");if(len<0){io->error=errno;close(io->fd);
and 'GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49'
fails with:
+ test_must_fail force_color git add -p
about to run diffFilter
attempting to xwrite() 224 bytes to a fd with revents flags 0x4
test_must_fail: died by signal 13: force_color git add -p
I don't understand why we get SIGPIPE right away instead of some error
that we can act upon (ECONNRESET?). FWIW, it fails the same way not
only on my box, but on Travis CI's Linux and OSX images as well.
https://travis-ci.org/szeder/git/jobs/636446843#L2937
Cc'ing Peff for all things SIGPIPE :) who also happens to be the
author of both pipe_command() and that now flaky test.
quoted hunk
The appropriate action here is to ignore that signal and treat it as a
regular end-of-file, otherwise the test will fail. In preparation for
such a test, introduce precisely this handling of SIGPIPE into the
built-in version of `git add -p`.
For good measure, teach the built-in `git add -i` the same trick: it
_also_ runs a loop waiting for input, and can receive a SIGPIPE just the
same (and wants to treat it as end-of-file, too).
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 3 +++
add-patch.c | 4 ++++
2 files changed, 7 insertions(+)
From: Jeff King <hidden> Date: 2020-01-13 18:33:16
On Mon, Jan 13, 2020 at 06:04:17PM +0100, SZEDER Gábor wrote:
After looking into it, the issue seems to be sending data to the
broken diffFilter process. So in that test the diff is "filtered"
through 'echo too-short', which exits real fast, and doesn't read its
standard input at all (well, apart from e.g. the usual kernel
buffering that might happen on a pipe between the two processes).
Making sure that the diffFilter process reads all the data before
exiting, i.e. changing it to:
test_config interactive.diffFilter "cat >/dev/null ; echo too-short" &&
made the test reliable, with over 2000 --stress repetitions, and that
with only a single "y" on 'git add's stdin.
Yeah, I agree the test should be changed. What you wrote above was my
first thought, too, but I think "sed 1d" is actually a more realistic
test (and is shorter and one fewer process).
Now, merely tweaking the test is clearly insufficient, because we not
only want the test to be realiable, but we want 'git add' to die
gracefully when users out there mess up their configuration.
I also agree that it would be nice to deal with this for real-world
cases. I suspect it's not something that would come up a lot, though.
Ignoring SIGPIPE can surely accomplish that, but I'm not sure about
the scope. I mean your patch seems to ignore SIGPIPE basically for
almost the whole 'git add -(i|p)' process, but perhaps it should be
limited only to the surroundings of the pipe_command() call running
the diffFilter, and be done as part of the next patch adding the 'if
(diff_filter)' block.
The scope there is probably OK in practice. In my opinion SIGPIPE is
usually _not_ what the behavior we want. If we're carefully checking our
write() return values, then we'd get EPIPE in such an instance and
behave appropriately. And if we're not checking our write() return
values, that's generally a bug that ought to be fixed.
The big exception is when we are writing copious output to stdout (or
the pager) via printf() or similar, and want to die rather than continue
writing output nobody will see. But I don't think git-add really counts
as generating a lot of output, where EPIPE could prevent us from doing
useless work (unlike, say, git-log).
Furthermore, I'm worried that by simply ignoring SIGPIPE we might just
ignore a more fundamental issue in pipe_command(): shouldn't that
function be smart enough not to write() to a fd that has no one on the
other side to read it in the first place?!
Maybe. As you noted below, checking for POLLERR is racy. Seeing that we
"can" write to an fd and doing it to discover what write() returns
(whether error or not) doesn't seem like the worst strategy. If the
caller cares about pipe death, then it needs to be handling SIGPIPE
anyway.
I really wish there was a way to set a handler for SIGPIPE that tells
_which_ descriptor caused it. Because I think logic like "die if it was
fd 1, ignore and let write() return EPIPE otherwise" is the behavior
we'd like. But I don't think there's a portable way to do so.
I've been tempted to say that we should just ignore SIGPIPE everywhere,
and convert even copious-output programs like git-log to just check for
errors (they could probably even just check ferror(stdout) for each
commit we output, if we didn't want to touch every printf call).
-Peff
From: Johannes Schindelin <hidden> Date: 2020-01-14 12:47:57
Hi Gábor,
On Mon, 13 Jan 2020, SZEDER Gábor wrote:
On Mon, Jan 13, 2020 at 08:29:22AM +0000, Johannes Schindelin via GitGitGadget wrote:
quoted
From: Johannes Schindelin <redacted>
As noticed by Gábor Szeder, if we want to run `git add -p` with
redirected input through `test_must_fail` in the test suite, we must
expect that a SIGPIPE can happen due to `stdin` coming to its end.
I don't think this issue is related to the redirected input: I
modified that flaky test to send "unlimited" data to 'git add's stdin,
i.e.:
/usr/bin/yes | test_must_fail force_color git add -p
and the test with --stress still failed with SIGPIPE all the same and
just as fast.
After looking into it, the issue seems to be sending data to the
broken diffFilter process.
Ouch. Thank you for investigating. For my education, how did you debug
this? I could not find a way to identify *what* caused that SIGPIPE...
So in that test the diff is "filtered" through 'echo too-short', which
exits real fast, and doesn't read its standard input at all (well, apart
from e.g. the usual kernel buffering that might happen on a pipe between
the two processes). Making sure that the diffFilter process reads all
the data before exiting, i.e. changing it to:
test_config interactive.diffFilter "cat >/dev/null ; echo too-short" &&
made the test reliable, with over 2000 --stress repetitions, and that
with only a single "y" on 'git add's stdin.
Ah, my diff filter simply ignores the `stdin`... That's easy enough to
fix, and since real-world diff filters probably won't just blatantly
ignore the input, I think it is legitimate to change the test.
Now, merely tweaking the test is clearly insufficient, because we not
only want the test to be realiable, but we want 'git add' to die
gracefully when users out there mess up their configuration.
I think it is sufficient to tweak the test, but I agree that a better
error message might be good when users out there mess up their
configuration.
Ignoring SIGPIPE can surely accomplish that, but I'm not sure about
the scope. I mean your patch seems to ignore SIGPIPE basically for
almost the whole 'git add -(i|p)' process, but perhaps it should be
limited only to the surroundings of the pipe_command() call running
the diffFilter, and be done as part of the next patch adding the 'if
(diff_filter)' block.
Right. Very heavy-handed, and probably inviting unwanted side effects.
quoted hunk
Furthermore, I'm worried that by simply ignoring SIGPIPE we might just
ignore a more fundamental issue in pipe_command(): shouldn't that
function be smart enough not to write() to a fd that has no one on the
other side to read it in the first place?!
So, when the diffFilter process exits unexpectedly early, then the
poll() call in pipe_command() -> pump_io() -> pump_io_round() returns
with success and usually sets 'revents' for the child process' stdin
to 12 (i.e. 'POLLOUT | POLLERR'; gah, how I hate unnamed constants :).
Unfortunately, at that point we don't take any special action on
POLLERR, but call xwrite() to try to write to the dead fd anyway,
which then promptly triggers SIGPIPE. (This is what usually happens
when stepping through the statements of those functions in a debugger,
and the diffFilter process has all the time in the world to exit.)
We could handle POLLERR with a patch like this:
--- >8 ---
Subject: run-command: handle POLLERR in pump_io_round() to reduce risk of SIGPIPE
@@ -1416,25 +1416,31 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)if(poll(pfd,pollsize,-1)<0){if(errno==EINTR)return1;die_errno("poll failed");}for(i=0;i<nr;i++){structio_pump*io=&slots[i];if(io->fd<0)continue;-if(!(io->pfd->revents&(POLLOUT|POLLIN|POLLHUP|POLLERR|POLLNVAL)))+if(io->pfd->revents&POLLERR){+io->error=ECONNRESET;/* What should we report to the caller? */+close(io->fd);+io->fd=-1;+continue;+}+if(!(io->pfd->revents&(POLLOUT|POLLIN|POLLHUP|POLLNVAL)))continue;if(io->type==POLLOUT){ssize_tlen=xwrite(io->fd,io->u.out.buf,io->u.out.len);if(len<0){io->error=errno;close(io->fd);io->fd=-1;}else{io->u.out.buf+=len;io->u.out.len-=len;--->8---
Unfortunately #1, this changes the error 'git add -p' dies with from:
error: mismatched output from interactive.diffFilter
to:
error: failed to run 'echo too-short'
It might affect other commands as well, but FWIW the test suite
doesn't catch any.
Hmm. My first impression is that the error message could be a bit better,
but that it is probably a good thing to have. It would have helped _me_
understand the issue at hand.
quoted hunk
Unfortunately #2, the above patch doesn't completely eliminates the
SIGPIPE, but only (greatly) reduces its probability. It is possible
that:
- poll() returns with success and indicating a writable fd without
any error, i.e. 'revents = 4'.
- the bogus diffFilter exits, closing its stdin.
- 'git add' attempts to xwrite() to the now closed fd, and triggers
a SIGPIPE right away.
This happens much rarer, 'GIT_TEST_ADD_I_USE_BUILTIN=1
./t3701-add-interactive.sh -r 39,49 --stress-jobs=<4*nr-of-cores>
--stress' tends to take over 200 repetitions. The patch below
reproduces it fairly reliably by adding two strategically-placed
sleep()s, with a bit of extra debug output:
--- >8 ---
@@ -1419,6 +1419,7 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)die_errno("poll failed");}+sleep(2);for(i=0;i<nr;i++){structio_pump*io=&slots[i];
@@ -1435,8 +1436,11 @@ static int pump_io_round(struct io_pump *slots, int nr, struct pollfd *pfd)continue;if(io->type==POLLOUT){-ssize_tlen=xwrite(io->fd,+ssize_tlen;+fprintf(stderr,"attempting to xwrite() %lu bytes to a fd with revents flags 0x%hx\n",io->u.out.len,io->pfd->revents);+len=xwrite(io->fd,io->u.out.buf,io->u.out.len);+fprintf(stderr,"after xwrite()\n");if(len<0){io->error=errno;close(io->fd);
and 'GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49'
fails with:
+ test_must_fail force_color git add -p
about to run diffFilter
attempting to xwrite() 224 bytes to a fd with revents flags 0x4
test_must_fail: died by signal 13: force_color git add -p
I don't understand why we get SIGPIPE right away instead of some error
that we can act upon (ECONNRESET?).
Isn't it buffered?
In any case, I would take the above-mentioned patch, even if it makes it
"only" less likely to hit `SIGPIPE`.
FWIW, it fails the same way not only on my box, but on Travis CI's Linux
and OSX images as well.
https://travis-ci.org/szeder/git/jobs/636446843#L2937
Cc'ing Peff for all things SIGPIPE :) who also happens to be the
author of both pipe_command() and that now flaky test.
I'll go with Peff's suggestion to use `sed 1d` instead of `echo
too-short`.
Thanks,
Dscho
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:00
This is the final leg of the journey to a fully built-in git add: the git
add -i and git add -p modes were re-implemented in C, but they lacked
support for a couple of config settings.
The one that sticks out most is the interactive.singleKey setting: it was
particularly hard to get to work, especially on Windows.
It also seems to be the setting that is incomplete already in the Perl
version of the interactive add command: while the name of the config setting
suggests that it applies to all of the interactive add, including the main
loop of git add --interactive and to the file selections in that command, it
does not. Only the git add --patch mode respects that setting.
As it is outside the purpose of the conversion of git-add--interactive.perl
to C, we will leave that loose end for some future date.
Changes since v3:
* Reverted that heavy-handed SIGPIPE handling.
* Instead, changed the diffFilter test case to process the standard input
instead of ignoring it.
(The range diff between v2 and v4 actually only shows the new patch 1/10
"t3701: adjust difffilter test".)
Changes since v2:
* Fixed the SIGPIPE issue pointed out by Gábor Szeder.
Changes since v1:
* Fixed the commit message where a copy/paste fail made it talk about
another GIT_TEST_* variable than the GIT_TEST_ADD_I_USE_BUILTIN one.
Johannes Schindelin (10):
t3701: adjust difffilter test
built-in add -p: support interactive.diffFilter
built-in add -p: handle diff.algorithm
terminal: make the code of disable_echo() reusable
terminal: accommodate Git for Windows' default terminal
terminal: add a new function to read a single keystroke
built-in add -p: respect the `interactive.singlekey` config setting
built-in add -p: handle Escape sequences in interactive.singlekey mode
built-in add -p: handle Escape sequences more efficiently
ci: include the built-in `git add -i` in the `linux-gcc` job
add-interactive.c | 19 +++
add-interactive.h | 4 +
add-patch.c | 57 ++++++++-
ci/run-build-and-tests.sh | 1 +
compat/terminal.c | 249 ++++++++++++++++++++++++++++++++++++-
compat/terminal.h | 3 +
t/t3701-add-interactive.sh | 2 +-
7 files changed, 326 insertions(+), 9 deletions(-)
base-commit: c480eeb574e649a19f27dc09a994e45f9b2c2622
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-175%2Fdscho%2Fadd-p-in-c-config-settings-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-175/dscho/add-p-in-c-config-settings-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/175
Range-diff vs v3:
1: 5e258a8d2b < -: ---------- built-in add -i/-p: treat SIGPIPE as EOF
-: ---------- > 1: e12df77e8a t3701: adjust difffilter test
2: 2a5951ecfe ! 2: 413a87bd79 built-in add -p: support interactive.diffFilter
@@ -35,9 +35,9 @@
strbuf_release(&header);
prefix_item_list_clear(&commands);
+ clear_add_i_state(&s);
- sigchain_pop(SIGPIPE);
return res;
+ }
diff --git a/add-interactive.h b/add-interactive.h
--- a/add-interactive.h
@@ -123,14 +123,13 @@
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
- sigchain_pop(SIGPIPE);
return -1;
}
+
@@
strbuf_release(&s.buf);
strbuf_release(&s.plain);
strbuf_release(&s.colored);
+ clear_add_i_state(&s.s);
- sigchain_pop(SIGPIPE);
return 0;
}
3: a2bce01818 = 3: 062c624547 built-in add -p: handle diff.algorithm
4: be40a37c0c = 4: 09a8946303 terminal: make the code of disable_echo() reusable
5: 233f23791c = 5: a81304cb76 terminal: accommodate Git for Windows' default terminal
6: 74593b5115 = 6: 8d9c703f3b terminal: add a new function to read a single keystroke
7: 197fe1e14a ! 7: 8ed4487ae4 built-in add -p: respect the `interactive.singlekey` config setting
@@ -48,9 +48,9 @@
--- a/add-patch.c
+++ b/add-patch.c
@@
+ #include "pathspec.h"
#include "color.h"
#include "diff.h"
- #include "sigchain.h"
+#include "compat/terminal.h"
enum prompt_mode_type {
8: 9ab381d539 = 8: cdc609f8fa built-in add -p: handle Escape sequences in interactive.singlekey mode
9: bdb6268b8b = 9: 80b0f2528d built-in add -p: handle Escape sequences more efficiently
10: c4195969a6 = 10: 7ab7ec62d0 ci: include the built-in `git add -i` in the `linux-gcc` job
--
gitgitgadget
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:00
From: Johannes Schindelin <redacted>
In 42f7d45428e (add--interactive: detect bogus diffFilter output,
2018-03-03), we added a test case that verifies that the diffFilter
feature complains appropriately when the output is too short.
In preparation for the upcoming change where the built-in `add -p` is
taught to respect that setting, let's adjust that test a little. The
problem is that `echo too-short` is configured as diffFilter, and it
does not read the `stdin`. When calling it through `pipe_command()`, it
is therefore possible that we try to feed the `diff` to it while it is
no longer listening, and we receive a `SIGPIPE`.
The Perl code apparently handles this in a way similar to an
end-of-file, but taking a step back, we realize that a diffFilter that
does not even _look_ at its standard input is very unrealistic. The
entire point of this feature is to transform the diff, not to ignore it
altogether.
So let's modify the test case to reflect that insight: instead of
printing some bogus text, let's use a diffFilter that deletes the first
line of the diff instead.
This still tests for the same thing, but it does not confuse the
built-in `add -p` with that `SIGPIPE`.
Helped-by: SZEDER Gábor [off-list ref]
Helped-by: Jeff King [off-list ref]
Signed-off-by: Johannes Schindelin <redacted>
---
t/t3701-add-interactive.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:02
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 12 ++++++++++++
add-interactive.h | 3 +++
add-patch.c | 33 +++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+)
@@ -407,6 +408,24 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)argv_array_clear(&args);if(res)returnerror(_("could not parse colored diff"));++if(diff_filter){+structchild_processfilter_cp=CHILD_PROCESS_INIT;++setup_child_process(s,&filter_cp,+diff_filter,NULL);+filter_cp.git_cmd=0;+filter_cp.use_shell=1;+strbuf_reset(&s->buf);+if(pipe_command(&filter_cp,+colored->buf,colored->len,+&s->buf,colored->len,+NULL,0)<0)+returnerror(_("failed to run '%s'"),+diff_filter);+strbuf_swap(colored,&s->buf);+}+strbuf_complete_line(colored);colored_p=colored->buf;colored_pend=colored_p+colored->len;
@@ -531,6 +550,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)colored_pend-colored_p);if(colored_eol)colored_p=colored_eol+1;+elseif(p!=pend)+/* colored shorter than non-colored? */+gotomismatched_output;elsecolored_p=colored_pend;
@@ -555,6 +577,15 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps)*/hunk->splittable_into++;+/* non-colored shorter than colored? */+if(colored_p!=colored_pend){+mismatched_output:+error(_("mismatched output from interactive.diffFilter"));+advise(_("Your filter must maintain a one-to-one correspondence\n"+"between its input and output lines."));+return-1;+}+return0;}
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:05
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
@@ -79,6 +98,37 @@ static void restore_term(void)staticintdisable_bits(DWORDbits){+if(use_stty){+structchild_processcp=CHILD_PROCESS_INIT;++argv_array_push(&cp.args,"stty");++if(bits&ENABLE_LINE_INPUT){+string_list_append(&stty_restore,"icanon");+argv_array_push(&cp.args,"-icanon");+}++if(bits&ENABLE_ECHO_INPUT){+string_list_append(&stty_restore,"echo");+argv_array_push(&cp.args,"-echo");+}++if(bits&ENABLE_PROCESSED_INPUT){+string_list_append(&stty_restore,"-ignbrk");+string_list_append(&stty_restore,"intr");+string_list_append(&stty_restore,"^c");+argv_array_push(&cp.args,"ignbrk");+argv_array_push(&cp.args,"intr");+argv_array_push(&cp.args,"");+}++if(run_command(&cp)==0)+return0;++/* `stty` could not be executed; access the Console directly */+use_stty=0;+}+hconin=CreateFile("CONIN$",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:07
From: Johannes Schindelin <redacted>
This recapitulates part of b5cc003253c8 (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 <redacted>
---
compat/terminal.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:08
From: Johannes Schindelin <redacted>
This job runs the test suite twice, once in regular mode, and once with
a whole slew of `GIT_TEST_*` variables set.
Now that the built-in version of `git add --interactive` is
feature-complete, let's also throw `GIT_TEST_ADD_I_USE_BUILTIN` into
that fray.
Signed-off-by: Johannes Schindelin <redacted>
---
ci/run-build-and-tests.sh | 1 +
1 file changed, 1 insertion(+)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:09
From: Johannes Schindelin <redacted>
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 <redacted>
---
add-interactive.c | 2 ++
add-interactive.h | 1 +
add-patch.c | 21 +++++++++++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:09
From: Johannes Schindelin <redacted>
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.
Note: just like `git-add--interactive.perl`, we do _not_ respect this
config setting in `git add -i`'s `diff` command, but _only_ in the
`patch` command.
Signed-off-by: Johannes Schindelin <redacted>
---
add-interactive.c | 5 +++++
add-interactive.h | 2 +-
add-patch.c | 3 +++
3 files changed, 9 insertions(+), 1 deletion(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:11
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++
compat/terminal.h | 3 +++
2 files changed, 58 insertions(+)
@@ -60,6 +60,11 @@ static int disable_echo(void)returndisable_bits(ECHO);}+staticintenable_non_canonical(void)+{+returndisable_bits(ICANON|ECHO);+}+#elif defined(GIT_WINDOWS_NATIVE)#define INPUT_PATH "CONIN$"
@@ -151,6 +156,10 @@ static int disable_echo(void)returndisable_bits(ENABLE_ECHO_INPUT);}+staticintenable_non_canonical(void)+{+returndisable_bits(ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT);+}#endif
@@ -198,6 +207,33 @@ char *git_terminal_prompt(const char *prompt, int echo)returnbuf.buf;}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+intch;++if(warning_displayed||enable_non_canonical()<0){+if(!warning_displayed){+warning("reading single keystrokes not supported on "+"this platform; reading line instead");+warning_displayed=1;+}++returnstrbuf_getline(buf,stdin);+}++strbuf_reset(buf);+ch=getchar();+if(ch==EOF){+restore_term();+returnEOF;+}++strbuf_addch(buf,ch);+restore_term();+return0;+}+#elsechar*git_terminal_prompt(constchar*prompt,intecho)
@@ -205,4 +241,23 @@ char *git_terminal_prompt(const char *prompt, int echo)returngetpass(prompt);}+intread_key_without_echo(structstrbuf*buf)+{+staticintwarning_displayed;+constchar*res;++if(!warning_displayed){+warning("reading single keystrokes not supported on this "+"platform; reading line instead");+warning_displayed=1;+}++res=getpass("");+strbuf_reset(buf);+if(!res)+returnEOF;+strbuf_addstr(buf,res);+return0;+}+#endif
@@ -3,4 +3,7 @@char*git_terminal_prompt(constchar*prompt,intecho);+/* Read a single keystroke, without echoing it to the terminal */+intread_key_without_echo(structstrbuf*buf);+#endif /* COMPAT_TERMINAL_H */
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:12
From: Johannes Schindelin <redacted>
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 <redacted>
---
compat/terminal.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
From: Johannes Schindelin via GitGitGadget <hidden> Date: 2020-01-14 18:44:14
From: Johannes Schindelin <redacted>
When `interactive.singlekey = true`, we react immediately to keystrokes,
even to Escape sequences (e.g. when pressing a cursor key).
The problem with Escape sequences is that we do not really know when
they are done, and as a heuristic we poll standard input for half a
second to make sure that we got all of it.
While waiting half a second is not asking for a whole lot, it can become
quite annoying over time, therefore with this patch, we read the
terminal capabilities (if available) and extract known Escape sequences
from there, then stop polling immediately when we detected that the user
pressed a key that generated such a known sequence.
This recapitulates the remaining part of b5cc003253c8 (add -i: ignore
terminal escape sequences, 2011-05-17).
Note: We do *not* query the terminal capabilities directly. That would
either require a lot of platform-specific code, or it would require
linking to a library such as ncurses.
Linking to a library in the built-ins is something we try very hard to
avoid (we even kicked the libcurl dependency to a non-built-in remote
helper, just to shave off a tiny fraction of a second from Git's startup
time). And the platform-specific code would be a maintenance nightmare.
Even worse: in Git for Windows' case, we would need to query MSYS2
pseudo terminals, which `git.exe` simply cannot do (because it is
intentionally *not* an MSYS2 program).
To address this, we simply spawn `infocmp -L -1` and parse its output
(which works even in Git for Windows, because that helper is included in
the end-user facing installations).
This is done only once, as in the Perl version, but it is done only when
the first Escape sequence is encountered, not upon startup of `git add
-i`; This saves on startup time, yet makes reacting to the first Escape
sequence slightly more sluggish. But it allows us to keep the
terminal-related code encapsulated in the `compat/terminal.c` file.
Signed-off-by: Johannes Schindelin <redacted>
---
compat/terminal.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
From: SZEDER Gábor <hidden> Date: 2020-01-17 14:32:45
On Mon, Jan 13, 2020 at 06:04:17PM +0100, SZEDER Gábor wrote:
and 'GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49'
fails with:
+ test_must_fail force_color git add -p
about to run diffFilter
attempting to xwrite() 224 bytes to a fd with revents flags 0x4
test_must_fail: died by signal 13: force_color git add -p
I don't understand why we get SIGPIPE right away instead of some error
that we can act upon (ECONNRESET?).
Doh', because it's a pipe, not a socket, that's why. pipe(7):
"If all file descriptors referring to the read end of a pipe have
been closed, then a write(2) will cause a SIGPIPE signal to be
generated for the calling process."
So ECONNRESET is definitely not the right error to set on POLLERR,
though I'm still not sure what the right one would be (perhaps
EPIPE?).
From: Jeff King <hidden> Date: 2020-01-17 18:58:39
On Fri, Jan 17, 2020 at 03:32:36PM +0100, SZEDER Gábor wrote:
On Mon, Jan 13, 2020 at 06:04:17PM +0100, SZEDER Gábor wrote:
quoted
and 'GIT_TEST_ADD_I_USE_BUILTIN=1 ./t3701-add-interactive.sh -r 39,49'
fails with:
+ test_must_fail force_color git add -p
about to run diffFilter
attempting to xwrite() 224 bytes to a fd with revents flags 0x4
test_must_fail: died by signal 13: force_color git add -p
I don't understand why we get SIGPIPE right away instead of some error
that we can act upon (ECONNRESET?).
Doh', because it's a pipe, not a socket, that's why. pipe(7):
"If all file descriptors referring to the read end of a pipe have
been closed, then a write(2) will cause a SIGPIPE signal to be
generated for the calling process."
So ECONNRESET is definitely not the right error to set on POLLERR,
though I'm still not sure what the right one would be (perhaps
EPIPE?).
Yes, if SIGPIPE is ignored, then that write() would produce EPIPE. So if
you're trying to emulate it via POLLERR, that would be accurate. Of
course it could fail for _other_ reasons, and I don't think we'd know
what those are without actually calling write(). Practically speaking,
though, if we know it's a pipe with a valid descriptor then any error is
basically equivalent to EPIPE (we don't care how, but for whatever
reason we couldn't write to the other end).
-Peff