Don't fflush(stdout) when it's not helpful
authorTheodore Ts'o <tytso@mit.edu>
Fri, 29 Jun 2007 17:40:46 +0000 (13:40 -0400)
committerJunio C Hamano <gitster@pobox.com>
Sun, 1 Jul 2007 03:16:12 +0000 (20:16 -0700)
This patch arose from a discussion started by Jim Meyering's patch
whose intention was to provide better diagnostics for failed writes.
Linus proposed a better way to do things, which also had the added
benefit that adding a fflush() to git-log-* operations and incremental
git-blame operations could improve interactive respose time feel, at
the cost of making things a bit slower when we aren't piping the
output to a downstream program.

This patch skips the fflush() calls when stdout is a regular file, or
if the environment variable GIT_FLUSH is set to "0".  This latter can
speed up a command such as:

GIT_FLUSH=0 strace -c -f -e write time git-rev-list HEAD | wc -l

a tiny amount.

Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git.txt
builtin-blame.c
builtin-rev-list.c
cache.h
log-tree.c
write_or_die.c

index 20b5b7bb48f75cea066857c6967a08203957d927..826914837bc0a3e1fa5f044f69b837abdd42cf8c 100644 (file)
@@ -396,6 +396,16 @@ other
 'GIT_PAGER'::
        This environment variable overrides `$PAGER`.
 
+'GIT_FLUSH'::
+       If this environment variable is set to "1", then commands such
+       as git-blame (in incremental mode), git-rev-list, git-log,
+       git-whatchanged, etc., will force a flush of the output stream
+       after each commit-oriented record have been flushed.   If this
+       variable is set to "0", the output of these commands will be done
+       using completely buffered I/O.   If this environment variable is
+       not set, git will choose buffered or record-oriented flushing
+       based on whether stdout appears to be redirected to a file or not.
+
 'GIT_TRACE'::
        If this variable is set to "1", "2" or "true" (comparison
        is case insensitive), git will print `trace:` messages on
index f7e2c13885a384d58c3481fa7259a64bf9525045..da23a6f9c99e1db691ba9329ea33a320b56b6afc 100644 (file)
@@ -1459,6 +1459,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                                printf("boundary\n");
                }
                write_filename_info(suspect->path);
+               maybe_flush_or_die(stdout, "stdout");
        }
 }
 
index 813aadf596df7fe2e61517915707717120842d74..86db8b03fe4295497cf011a75617d6dc8f172c64 100644 (file)
@@ -100,7 +100,7 @@ static void show_commit(struct commit *commit)
                printf("%s%c", buf, hdr_termination);
                free(buf);
        }
-       fflush(stdout);
+       maybe_flush_or_die(stdout, "stdout");
        if (commit->parents) {
                free_commit_list(commit->parents);
                commit->parents = NULL;
diff --git a/cache.h b/cache.h
index ed83d92c5a2735b2b7f8a7fc06276acbf0df8b18..0525c4ee55a05304a6b2018cc27933baa5510aba 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -532,6 +532,8 @@ extern char git_default_name[MAX_GITNAME];
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
 
+/* IO helper functions */
+extern void maybe_flush_or_die(FILE *, const char *);
 extern int copy_fd(int ifd, int ofd);
 extern int read_in_full(int fd, void *buf, size_t count);
 extern int write_in_full(int fd, const void *buf, size_t count);
index 0cf21bc05180b577a5f14dc57031c260e7c44334..ced3f332ef767e39239874edd68c8445ba6991d9 100644 (file)
@@ -408,5 +408,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
                shown = 1;
        }
        opt->loginfo = NULL;
+       maybe_flush_or_die(stdout, "stdout");
        return shown;
 }
index 5c4bc8515ab9484131de7e065e08657315004f8c..e125e11d3b63e3dab9077d7b414e83e7ff7d16ad 100644 (file)
@@ -1,5 +1,45 @@
 #include "cache.h"
 
+/*
+ * Some cases use stdio, but want to flush after the write
+ * to get error handling (and to get better interactive
+ * behaviour - not buffering excessively).
+ *
+ * Of course, if the flush happened within the write itself,
+ * we've already lost the error code, and cannot report it any
+ * more. So we just ignore that case instead (and hope we get
+ * the right error code on the flush).
+ *
+ * If the file handle is stdout, and stdout is a file, then skip the
+ * flush entirely since it's not needed.
+ */
+void maybe_flush_or_die(FILE *f, const char *desc)
+{
+       static int skip_stdout_flush = -1;
+       struct stat st;
+       char *cp;
+
+       if (f == stdout) {
+               if (skip_stdout_flush < 0) {
+                       cp = getenv("GIT_FLUSH");
+                       if (cp)
+                               skip_stdout_flush = (atoi(cp) == 0);
+                       else if ((fstat(fileno(stdout), &st) == 0) &&
+                                S_ISREG(st.st_mode))
+                               skip_stdout_flush = 1;
+                       else
+                               skip_stdout_flush = 0;
+               }
+               if (skip_stdout_flush && !ferror(f))
+                       return;
+       }
+       if (fflush(f)) {
+               if (errno == EPIPE)
+                       exit(0);
+               die("write failure on %s: %s", desc, strerror(errno));
+       }
+}
+
 int read_in_full(int fd, void *buf, size_t count)
 {
        char *p = buf;