From: Christian Couder <hidden> Date: 2016-06-16 02:18:57
This replaces run_apply() implementation with a new one that
uses the apply api that has been previously prepared in
apply.c and apply.h.
This shoud improve performance a lot in certain cases.
As the previous implementation was creating a new `git apply`
process to apply each patch, it could be slow on systems like
Windows where it is costly to create new processes.
Also the new `git apply` process had to read the index from
disk, and when the process was done the calling process
discarded its own index and read back from disk the new
index that had been created by the `git apply` process.
This could be very inefficient with big repositories that
have big index files, especially when the system decided
that it was a good idea to run the `git apply` processes on
a different processor core.
Also eliminating index reads enables further performance
improvements by using:
`git update-index --split-index`
Signed-off-by: Christian Couder <redacted>
---
builtin/am.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 85 insertions(+), 18 deletions(-)
@@ -1522,39 +1523,105 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)*/staticintrun_apply(conststructam_state*state,constchar*index_file){-structchild_processcp=CHILD_PROCESS_INIT;--cp.git_cmd=1;--if(index_file)-argv_array_pushf(&cp.env_array,"GIT_INDEX_FILE=%s",index_file);+structargv_arrayapply_paths=ARGV_ARRAY_INIT;+structargv_arrayapply_opts=ARGV_ARRAY_INIT;+structapply_stateapply_state;+intsave_stdout_fd,save_stderr_fd;+intres,opts_left;+char*save_index_file;++structoptionam_apply_options[]={+{OPTION_CALLBACK,0,"whitespace",&apply_state,N_("action"),+N_("detect new or modified lines that have whitespace errors"),+0,apply_option_parse_whitespace},+{OPTION_CALLBACK,0,"ignore-space-change",&apply_state,NULL,+N_("ignore changes in whitespace when finding context"),+PARSE_OPT_NOARG,apply_option_parse_space_change},+{OPTION_CALLBACK,0,"ignore-whitespace",&apply_state,NULL,+N_("ignore changes in whitespace when finding context"),+PARSE_OPT_NOARG,apply_option_parse_space_change},+{OPTION_CALLBACK,0,"directory",&apply_state,N_("root"),+N_("prepend <root> to all filenames"),+0,apply_option_parse_directory},+{OPTION_CALLBACK,0,"exclude",&apply_state,N_("path"),+N_("don't apply changes matching the given path"),+0,apply_option_parse_exclude},+{OPTION_CALLBACK,0,"include",&apply_state,N_("path"),+N_("apply changes matching the given path"),+0,apply_option_parse_include},+OPT_INTEGER('C',NULL,&apply_state.p_context,+N_("ensure at least <n> lines of context match")),+{OPTION_CALLBACK,'p',NULL,&apply_state,N_("num"),+N_("remove <num> leading slashes from traditional diff paths"),+0,apply_option_parse_p},+OPT_BOOL(0,"reject",&apply_state.apply_with_reject,+N_("leave the rejected hunks in corresponding *.rej files")),+OPT_END()+};/**Ifweareallowedtofallbackon3-waymerge,don'tgivefalse*errorsduringtheinitialattempt.*/+if(state->threeway&&!index_file){-cp.no_stdout=1;-cp.no_stderr=1;+save_stdout_fd=dup(1);+dup_devnull(1);+save_stderr_fd=dup(2);+dup_devnull(2);}-argv_array_push(&cp.args,"apply");+if(index_file){+save_index_file=get_index_file();+set_index_file((char*)index_file);+}-argv_array_pushv(&cp.args,state->git_apply_opts.argv);+if(init_apply_state(&apply_state,NULL))+die("init_apply_state() failed");++argv_array_push(&apply_opts,"apply");+argv_array_pushv(&apply_opts,state->git_apply_opts.argv);++opts_left=parse_options(apply_opts.argc,apply_opts.argv,+NULL,am_apply_options,NULL,0);++if(opts_left!=0)+die("unknown option passed thru to git apply");if(index_file)-argv_array_push(&cp.args,"--cached");+apply_state.cached=1;else-argv_array_push(&cp.args,"--index");+apply_state.check_index=1;-argv_array_push(&cp.args,am_path(state,"patch"));+if(check_apply_state(&apply_state,0))+die("check_apply_state() failed");-if(run_command(&cp))-return-1;+argv_array_push(&apply_paths,am_path(state,"patch"));-/* Reload index as git-apply will have modified it. */-discard_cache();-read_cache_from(index_file?index_file:get_index_file());+res=apply_all_patches(&apply_state,apply_paths.argc,apply_paths.argv,0);++/* Restore stdout and stderr */+if(state->threeway&&!index_file){+dup2(save_stdout_fd,1);+close(save_stdout_fd);+dup2(save_stderr_fd,2);+close(save_stderr_fd);+}++if(index_file)+set_index_file(save_index_file);++argv_array_clear(&apply_paths);+argv_array_clear(&apply_opts);++if(res)+returnres;++if(index_file){+/* Reload index as apply_all_patches() will have modified it. */+discard_cache();+read_cache_from(index_file);+}return0;}
From: Johannes Schindelin <hidden> Date: 2016-06-16 02:18:58
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
[...]
/*
* If we are allowed to fall back on 3-way merge, don't give false
* errors during the initial attempt.
*/
+
if (state->threeway && !index_file) {
- cp.no_stdout = 1;
- cp.no_stderr = 1;
+ save_stdout_fd = dup(1);
+ dup_devnull(1);
+ save_stderr_fd = dup(2);
+ dup_devnull(2);
I wonder. It should be possible to teach the apply function to be quiet by
default, yes? That would be more elegant than dup()ing back and forth.
Ciao,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-06-16 02:18:58
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
quoted hunk
@@ -4734,16 +4737,22 @@ int apply_all_patches(struct apply_state *state, read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options);- if (res < 0)+ if (res < 0) {+ if (state->lock_file)+ rollback_lock_file(state->lock_file); return -1;+ } errs |= res; close(fd);
In case of error, this leaves fd open, which in the end will prevent the
"patch" file, and hence the "rebase-apply/" directory from being removed
on Windows. This triggered a failure of t4014 here (and possibly more, but
it took me quite a while to track this down, what with builtin/am.c's
am_destroy() not bothering at all to check the return value of
remove_dir_recursively(), resulting in the error to be caught only much,
much later).
Could you please review all open()/close() and fopen()/fclose() calls in
your patch series, to make sure that there are no mistakes? A passing test
suite does not really make me confident here, as our code coverage is not
quite 100%.
Thanks,
Dscho
From: Johannes Schindelin <hidden> Date: 2016-06-16 02:19:07
Hi,
apparently this mail never made it to the list???
On Mon, 25 Apr 2016, Johannes Schindelin wrote:
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
quoted
@@ -4734,16 +4737,22 @@ int apply_all_patches(struct apply_state *state, read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options);- if (res < 0)+ if (res < 0) {+ if (state->lock_file)+ rollback_lock_file(state->lock_file); return -1;+ } errs |= res; close(fd);
In case of error, this leaves fd open, which in the end will prevent the
"patch" file, and hence the "rebase-apply/" directory from being removed
on Windows. This triggered a failure of t4014 here (and possibly more, but
it took me quite a while to track this down, what with builtin/am.c's
am_destroy() not bothering at all to check the return value of
remove_dir_recursively(), resulting in the error to be caught only much,
much later).
Could you please review all open()/close() and fopen()/fclose() calls in
your patch series, to make sure that there are no mistakes? A passing test
suite does not really make me confident here, as our code coverage is not
quite 100%.
Thanks,
Dscho
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
quoted
@@ -4734,16 +4737,22 @@ int apply_all_patches(struct apply_state *state, read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options);- if (res < 0)+ if (res < 0) {+ if (state->lock_file)+ rollback_lock_file(state->lock_file); return -1;+ } errs |= res; close(fd);
In case of error, this leaves fd open, which in the end will prevent the
"patch" file, and hence the "rebase-apply/" directory from being removed
on Windows. This triggered a failure of t4014 here (and possibly more, but
it took me quite a while to track this down, what with builtin/am.c's
am_destroy() not bothering at all to check the return value of
remove_dir_recursively(), resulting in the error to be caught only much,
much later).
Could you please review all open()/close() and fopen()/fclose() calls in
your patch series, to make sure that there are no mistakes? A passing test
suite does not really make me confident here, as our code coverage is not
quite 100%.
Thanks,
Dscho
From: Christian Couder <hidden> Date: 2016-06-16 02:19:09
Hi Dscho,
On Mon, Apr 25, 2016 at 6:06 PM, Johannes Schindelin
[off-list ref] wrote:
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
quoted
@@ -4734,16 +4737,22 @@ int apply_all_patches(struct apply_state *state, read_stdin = 0; set_default_whitespace_mode(state); res = apply_patch(state, fd, arg, options);- if (res < 0)+ if (res < 0) {+ if (state->lock_file)+ rollback_lock_file(state->lock_file); return -1;+ } errs |= res; close(fd);
In case of error, this leaves fd open, which in the end will prevent the
"patch" file, and hence the "rebase-apply/" directory from being removed
on Windows. This triggered a failure of t4014 here (and possibly more, but
it took me quite a while to track this down, what with builtin/am.c's
am_destroy() not bothering at all to check the return value of
remove_dir_recursively(), resulting in the error to be caught only much,
much later).
Sorry about that and thanks for tracking down the source of the test failure.
I fixed this by moving the "close(fd)" call just after the "apply_patch()" call.
Could you please review all open()/close() and fopen()/fclose() calls in
your patch series, to make sure that there are no mistakes? A passing test
suite does not really make me confident here, as our code coverage is not
quite 100%.
Ok, I will have another look at the 2 other places where there are
open()/close() or fopen()/fclose() calls.
Thanks,
Christian.
From: Christian Couder <hidden> Date: 2016-06-16 02:19:11
Hi Dscho,
On Mon, Apr 25, 2016 at 5:03 PM, Johannes Schindelin
[off-list ref] wrote:
Hi Chris,
On Sun, 24 Apr 2016, Christian Couder wrote:
quoted
[...]
/*
* If we are allowed to fall back on 3-way merge, don't give false
* errors during the initial attempt.
*/
+
if (state->threeway && !index_file) {
- cp.no_stdout = 1;
- cp.no_stderr = 1;
+ save_stdout_fd = dup(1);
+ dup_devnull(1);
+ save_stderr_fd = dup(2);
+ dup_devnull(2);
I wonder. It should be possible to teach the apply function to be quiet by
default, yes? That would be more elegant than dup()ing back and forth.
Yes, it could be possible, but it could mean many changes not only in
the apply functions, but in possibly many other places as well. I
didn't check, but for example if an apply function calls a function
from another part of git and this function uses error(...) in case of
error, I would have to change this function too.
I could also introduce a hack like a global variable that would tell
error() to shut up, but I am not sure that would be more elegant.
Thanks,
Christian.
The #ifndef GIT_WINDOWS_NATIVE rings very, very loud alarm bells.
Yeah, but I must say that I don't know what I should do about this.
Do you have a suggestion? Should I try to implement the same function
for Windows?
No, you should change the code that requires that ugly dup()ing so that it
can be configured to shut up.
After taking a look, it looks like a routine that does nothing could
be passed to set_error_routine() and that could do part of the trick.
This part might not be too ugly, but it would anyway be more complex,
less close to what the code is doing now and more error prone, as one
also need to make sure that for example no warning() or
fprintf(stderr, ...) are called and nothing is printed on stdout.
By the way I took a look and there are 11 calls to fprintf(stderr,
...) and 10 calls to warning() in different places in builtin/apply.c.
There might also be such calls in functions outside builtin/apply.c
that are called by the functions in builtin/apply.c.
So I'd much rather keep doing what I am doing now. If you or someone
else want to contribute patches on top of the series to do it in
another way, maybe they might be integrated at the same time by Junio,
so that the whole thing would appear in the same release and there
would be no feature discrepancy between Windows and the other
platforms, and you wouldn't need to implement anything special for
Windows.
But anyway, even though I don't know much about Windows, I think if
you have some code already in compat/mingw.c to handle redirections,
it might be easier and safer overall to just implement the
redirections in Windows.
Thanks,
Christian.
The #ifndef GIT_WINDOWS_NATIVE rings very, very loud alarm bells.
Yeah, but I must say that I don't know what I should do about this.
Do you have a suggestion? Should I try to implement the same function
for Windows?
No, you should change the code that requires that ugly dup()ing so that it
can be configured to shut up.
After taking a look, it looks like a routine that does nothing could
be passed to set_error_routine() and that could do part of the trick.
This part might not be too ugly, but it would anyway be more complex,
less close to what the code is doing now and more error prone, as one
also need to make sure that for example no warning() or
fprintf(stderr, ...) are called and nothing is printed on stdout.
I am afraid that you *have* to do that, though, if you truly want to
libify the code.
Of course you can go with really ugly workarounds instead. Something like
a global flag that die() and error() and warning() respect. It would
incur some technical debt, but it would make your life easier in the short
run.
Both the real solution and the workaround would be better than the current
version of the patches that dup() back and forth, just to avoid addressing
the real problem.
Ciao,
Dscho
From: Christian Couder <hidden> Date: 2016-06-16 02:19:14
Hi Dscho,
On Sat, May 7, 2016 at 2:13 PM, Johannes Schindelin
[off-list ref] wrote:
Hi Chris,
On Sat, 7 May 2016, Christian Couder wrote:
quoted
On Fri, May 6, 2016 at 5:34 PM, Johannes Schindelin
[off-list ref] wrote:
quoted
No, you should change the code that requires that ugly dup()ing so that it
can be configured to shut up.
After taking a look, it looks like a routine that does nothing could
be passed to set_error_routine() and that could do part of the trick.
This part might not be too ugly, but it would anyway be more complex,
less close to what the code is doing now and more error prone, as one
also need to make sure that for example no warning() or
fprintf(stderr, ...) are called and nothing is printed on stdout.
I am afraid that you *have* to do that, though, if you truly want to
libify the code.
Of course you can go with really ugly workarounds instead. Something like
a global flag that die() and error() and warning() respect. It would
incur some technical debt, but it would make your life easier in the short
run.
Both the real solution and the workaround would be better than the current
version of the patches that dup() back and forth, just to avoid addressing
the real problem.
The code that is now in master in builtin/am.c does:
if (state->threeway && !index_file) {
cp.no_stdout = 1;
cp.no_stderr = 1;
}
and in run-command.c there is already:
if (cmd->no_stdout)
dup_devnull(1);
[...]
if (cmd->no_stderr)
dup_devnull(2);
for Linux and the following for Windows:
if (cmd->no_stderr)
fherr = open("/dev/null", O_RDWR);
[...]
if (cmd->no_stdout)
fhout = open("/dev/null", O_RDWR);
so the current code is already using dup_devnull() for Linux that you
don't want me to use, and it looks like there is already a simple way
to do that on Windows.
So what's the problem? Isn't it just that you don't want a
dup_devnull() for Windows that would be a few lines long?
You keep saying that what is done in this patch is "ugly" or that
there is a "real problem", but frankly I don't see why. Could you
explain exactly why?
Because the more I look at it, the more it looks to me like the
solution that is the simplest (even for Windows), the safest and the
closest to what the current code is doing.
Thanks,
Christian.
From: Johannes Schindelin <hidden> Date: 2016-06-16 02:19:14
Hi Chris,
On Sat, 7 May 2016, Christian Couder wrote:
On Sat, May 7, 2016 at 2:13 PM, Johannes Schindelin
[off-list ref] wrote:
quoted
On Sat, 7 May 2016, Christian Couder wrote:
quoted
On Fri, May 6, 2016 at 5:34 PM, Johannes Schindelin
[off-list ref] wrote:
quoted
No, you should change the code that requires that ugly dup()ing so
that it can be configured to shut up.
After taking a look, it looks like a routine that does nothing could
be passed to set_error_routine() and that could do part of the trick.
This part might not be too ugly, but it would anyway be more complex,
less close to what the code is doing now and more error prone, as one
also need to make sure that for example no warning() or
fprintf(stderr, ...) are called and nothing is printed on stdout.
I am afraid that you *have* to do that, though, if you truly want to
libify the code.
Of course you can go with really ugly workarounds instead. Something
like a global flag that die() and error() and warning() respect. It
would incur some technical debt, but it would make your life easier in
the short run.
Both the real solution and the workaround would be better than the
current version of the patches that dup() back and forth, just to
avoid addressing the real problem.
The code that is now in master in builtin/am.c does:
if (state->threeway && !index_file) {
cp.no_stdout = 1;
cp.no_stderr = 1;
}
and in run-command.c there is already:
if (cmd->no_stdout)
dup_devnull(1);
[...]
if (cmd->no_stderr)
dup_devnull(2);
Of course it does that. Because there is no other way, that's why: you
cannot change the code that is spawned by start_command().
for Linux and the following for Windows:
if (cmd->no_stderr)
fherr = open("/dev/null", O_RDWR);
[...]
if (cmd->no_stdout)
fhout = open("/dev/null", O_RDWR);
And it is very well contained on Windows. No other callers. The code
is limited to run_command.c.
so the current code is already using dup_devnull() for Linux that you
don't want me to use, and it looks like there is already a simple way
to do that on Windows.
The difference between the code in master and what your patches try to do
is that in the latter case, you want to dup() just for a while, to shut up
a code path that is not only known very well, but our very own code that
is easily changed, only to dup() it *back* in the end.
The claim is that this libifies the procedure. But it makes the code
really nasty for use as a library: if this is run in a thread (and you
know that we are going to have to do this in the near future, for
performance reasons), it will completely mess up all the other threads
because it messes with the global file descriptors.
And that is why it is ugly: it incurs an enormous technical debt that will
make code changes substantially more complicated down the road.
In essence, you save yourself a little time by sloppily dup()ing back and
forth. At the expense of making the life much harder for the developer who
needs to use your code as a library function.
And actually, hiding even fatal errors might be an ugly side effect of the
current implementation, an unfortunate implementation detail, really, not
something we want to preserve when libifying the code. And actually,
dup()ing the *caller's* stdout is not exactly preserving the current
behavior that dup()s the *called process'* stdout.
So yes, I find the proposed patch inelegant.
If others are okay with this, I will shut up. But I have to point out that
it is ugly code, plain and simple, that silences an entire global file
descriptor, just temporarily, only to avoid a careful set of patches that
introduces a silent mode to the library functions that need to be called,
which might even facilitate other libifying efforts.
I hope you do not take this as a personal attack. It is not intended as
such. It is intended to help end up with the best possible code quality.
Ciao,
Johannes
On Sun, May 8, 2016 at 1:33 PM, Johannes Schindelin
[off-list ref] wrote:
The claim is that this libifies the procedure. But it makes the code
really nasty for use as a library: if this is run in a thread (and you
know that we are going to have to do this in the near future, for
performance reasons), it will completely mess up all the other threads
because it messes with the global file descriptors.
I vote one step at a time, leave multi-thread support for future.
There's a lot more shared state than file descriptors anyway, at least
there are object db and index access and probably a couple of hidden
static variables somewhere. And I'm not sure if multi-thread really
helps here. Are we really CPU-bound? If object inflation causes that
(wild guess), can we just inflate ahead in some separate process and
pass the result back?
--
Duy
From: Johannes Schindelin <hidden> Date: 2016-06-16 02:19:15
Hi Duy,
On Sun, 8 May 2016, Duy Nguyen wrote:
On Sun, May 8, 2016 at 1:33 PM, Johannes Schindelin
[off-list ref] wrote:
quoted
The claim is that this libifies the procedure. But it makes the code
really nasty for use as a library: if this is run in a thread (and you
know that we are going to have to do this in the near future, for
performance reasons), it will completely mess up all the other threads
because it messes with the global file descriptors.
I vote one step at a time, leave multi-thread support for future.
Oh, but I never said that we have to do that now!
All I said was that using this dup() dance instead of truly libifying the
functions would slam the door almost shut for future multi-threading.
Which is a strong hint in my book that we should *not* do that dup()
dance, but fix our code by introducing a silent mode.
There's a lot more shared state than file descriptors anyway, at least
there are object db and index access and probably a couple of hidden
static variables somewhere.
Sure. And do we change those shared states temporarily in our functions?
No, we don't. The object db is not made temporarily inaccessible while a
certain function runs. The index access is not temporarily disabled while
a certain function runs.
And this is what that dup() dance does: it disables *all* output, not only
from the current thread.
And I'm not sure if multi-thread really helps here. Are we really
CPU-bound? If object inflation causes that (wild guess), can we just
inflate ahead in some separate process and pass the result back?
Again. I did *not* suggest to introduce multi-threading. I was making a
case for *avoiding* that ugly dup() to /dev/null and then dup() it back to
the original state. That would just ask for unintended side effects.
Ciao,
Dscho