Skip to content

Commit 8676eb4

Browse files
Linus TorvaldsJunio C Hamano
authored andcommitted
Make git diff-generation use a simpler spawn-like interface
Instead of depending of fork() and execve() and doing things in between the two, make the git diff functions do everything up front, and then do a single "spawn_prog()" invocation to run the actual external diff program (if any is even needed). This actually ends up simplifying the code, and should make it much easier to make it efficient under broken operating systems (read: Windows). Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent ac5f7c6 commit 8676eb4

File tree

1 file changed

+80
-58
lines changed

1 file changed

+80
-58
lines changed

diff.c

Lines changed: 80 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,12 @@ static void emit_rewrite_diff(const char *name_a,
178178
copy_file('+', temp[1].name);
179179
}
180180

181-
static void builtin_diff(const char *name_a,
181+
static const char *builtin_diff(const char *name_a,
182182
const char *name_b,
183183
struct diff_tempfile *temp,
184184
const char *xfrm_msg,
185-
int complete_rewrite)
185+
int complete_rewrite,
186+
const char **args)
186187
{
187188
int i, next_at, cmd_size;
188189
const char *const diff_cmd = "diff -L%s -L%s";
@@ -242,19 +243,24 @@ static void builtin_diff(const char *name_a,
242243
}
243244
if (xfrm_msg && xfrm_msg[0])
244245
puts(xfrm_msg);
246+
/*
247+
* we do not run diff between different kind
248+
* of objects.
249+
*/
245250
if (strncmp(temp[0].mode, temp[1].mode, 3))
246-
/* we do not run diff between different kind
247-
* of objects.
248-
*/
249-
exit(0);
251+
return NULL;
250252
if (complete_rewrite) {
251-
fflush(NULL);
252253
emit_rewrite_diff(name_a, name_b, temp);
253-
exit(0);
254+
return NULL;
254255
}
255256
}
256-
fflush(NULL);
257-
execlp("/bin/sh","sh", "-c", cmd, NULL);
257+
258+
/* This is disgusting */
259+
*args++ = "sh";
260+
*args++ = "-c";
261+
*args++ = cmd;
262+
*args = NULL;
263+
return "/bin/sh";
258264
}
259265

260266
struct diff_filespec *alloc_filespec(const char *path)
@@ -559,6 +565,40 @@ static void remove_tempfile_on_signal(int signo)
559565
raise(signo);
560566
}
561567

568+
static int spawn_prog(const char *pgm, const char **arg)
569+
{
570+
pid_t pid;
571+
int status;
572+
573+
fflush(NULL);
574+
pid = fork();
575+
if (pid < 0)
576+
die("unable to fork");
577+
if (!pid) {
578+
execvp(pgm, (char *const*) arg);
579+
exit(255);
580+
}
581+
582+
while (waitpid(pid, &status, 0) < 0) {
583+
if (errno == EINTR)
584+
continue;
585+
return -1;
586+
}
587+
588+
/* Earlier we did not check the exit status because
589+
* diff exits non-zero if files are different, and
590+
* we are not interested in knowing that. It was a
591+
* mistake which made it harder to quit a diff-*
592+
* session that uses the git-apply-patch-script as
593+
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
594+
* should also exit non-zero only when it wants to
595+
* abort the entire diff-* session.
596+
*/
597+
if (WIFEXITED(status) && !WEXITSTATUS(status))
598+
return 0;
599+
return -1;
600+
}
601+
562602
/* An external diff command takes:
563603
*
564604
* diff-cmd name infile1 infile1-sha1 infile1-mode \
@@ -573,9 +613,9 @@ static void run_external_diff(const char *pgm,
573613
const char *xfrm_msg,
574614
int complete_rewrite)
575615
{
616+
const char *spawn_arg[10];
576617
struct diff_tempfile *temp = diff_temp;
577-
pid_t pid;
578-
int status;
618+
int retval;
579619
static int atexit_asked = 0;
580620
const char *othername;
581621

@@ -592,59 +632,41 @@ static void run_external_diff(const char *pgm,
592632
signal(SIGINT, remove_tempfile_on_signal);
593633
}
594634

595-
fflush(NULL);
596-
pid = fork();
597-
if (pid < 0)
598-
die("unable to fork");
599-
if (!pid) {
600-
if (pgm) {
601-
if (one && two) {
602-
const char *exec_arg[10];
603-
const char **arg = &exec_arg[0];
604-
*arg++ = pgm;
605-
*arg++ = name;
606-
*arg++ = temp[0].name;
607-
*arg++ = temp[0].hex;
608-
*arg++ = temp[0].mode;
609-
*arg++ = temp[1].name;
610-
*arg++ = temp[1].hex;
611-
*arg++ = temp[1].mode;
612-
if (other) {
613-
*arg++ = other;
614-
*arg++ = xfrm_msg;
615-
}
616-
*arg = NULL;
617-
execvp(pgm, (char *const*) exec_arg);
635+
if (pgm) {
636+
const char **arg = &spawn_arg[0];
637+
if (one && two) {
638+
*arg++ = pgm;
639+
*arg++ = name;
640+
*arg++ = temp[0].name;
641+
*arg++ = temp[0].hex;
642+
*arg++ = temp[0].mode;
643+
*arg++ = temp[1].name;
644+
*arg++ = temp[1].hex;
645+
*arg++ = temp[1].mode;
646+
if (other) {
647+
*arg++ = other;
648+
*arg++ = xfrm_msg;
618649
}
619-
else
620-
execlp(pgm, pgm, name, NULL);
650+
} else {
651+
*arg++ = pgm;
652+
*arg++ = name;
621653
}
622-
/*
623-
* otherwise we use the built-in one.
624-
*/
625-
if (one && two)
626-
builtin_diff(name, othername, temp, xfrm_msg,
627-
complete_rewrite);
628-
else
654+
*arg = NULL;
655+
} else {
656+
if (one && two) {
657+
pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg);
658+
} else
629659
printf("* Unmerged path %s\n", name);
630-
exit(0);
631660
}
632-
if (waitpid(pid, &status, 0) < 0 ||
633-
!WIFEXITED(status) || WEXITSTATUS(status)) {
634-
/* Earlier we did not check the exit status because
635-
* diff exits non-zero if files are different, and
636-
* we are not interested in knowing that. It was a
637-
* mistake which made it harder to quit a diff-*
638-
* session that uses the git-apply-patch-script as
639-
* the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
640-
* should also exit non-zero only when it wants to
641-
* abort the entire diff-* session.
642-
*/
643-
remove_tempfile();
661+
662+
retval = 0;
663+
if (pgm)
664+
retval = spawn_prog(pgm, spawn_arg);
665+
remove_tempfile();
666+
if (retval) {
644667
fprintf(stderr, "external diff died, stopping at %s.\n", name);
645668
exit(1);
646669
}
647-
remove_tempfile();
648670
}
649671

650672
static void diff_fill_sha1_info(struct diff_filespec *one)

0 commit comments

Comments
 (0)