{"thread":{"id":"23264","subject":"[RFC PATCH 0/2] gitk --color-words","startedAt":"2010-03-31T00:52:47Z","lastAt":"2010-04-05T10:21:37Z","messageCount":11,"participants":["Thomas Rast","Junio C Hamano","Miles Bader","Paul Mackerras"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"138240","messageId":"cover.1269996525.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":null,"subject":"[RFC PATCH 0/2] gitk --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-03-31T00:52:47Z","receivedAt":"2010-03-31T00:52:47Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This little pair of patches teaches gitk a color-words diff view.\n\nTo get there, I finally got rid of the assumption that --color-words\nonly ever needs to output ANSI(?) color sequences.  Hopefully this\nwill help other people to use --color-words in nice frontends.\n\nIt uses an ad-hoc output format where newlines in the output are\ninsignificant (so as to be able to speak about added/removed words)\nand newlines from the input are output as a line with only a '~'.\n\nRFC among other things because\n\n* I chose ~ more or less randomly.  Do any diff implementations out\n  there use it for some other purpose?\n\n* The --color-words code was the only user of color_fwrite_lines() so\n  I tweaked it according to my needs.  But that makes it highly\n  diff-specific again, so it's not in color.c any more.  Did anyone\n  else intend to use it?\n\n* The Tk part of the patch is 95% voodoo of the\n  cut&paste-from-elsewhere vintage and I have no clue whether it works\n  only by accident\n\n* I still need to do some things: add docs for --porcelain, add tests\n  to the first patch, move the gitk patch to affect only the subtree\n\n* I'm not even burning midnight oil any more\n\nThomas Rast (2):\n  Add diff --porcelain option for --color-words\n  gitk: add the equivalent of diff --color-words\n\n color.c       |   28 ---------------\n color.h       |    1 -\n diff.c        |  105 +++++++++++++++++++++++++++++++++++++++++++++++---------\n diff.h        |    1 +\n gitk-git/gitk |   29 +++++++++++++++-\n 5 files changed, 116 insertions(+), 48 deletions(-)\n"},{"id":"138241","messageId":"edaae0ea27b4b72e07af39a565dfceef7608af73.1269996525.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":"cover.1269996525.git.trast@student.ethz.ch","subject":"[PATCH 1/2] Add diff --porcelain option for --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-03-31T00:52:48Z","receivedAt":"2010-03-31T00:52:48Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This teaches the --color-words mode a new option --porcelain that\ndisables color mode again and instead uses an ad-hoc format for the\nword diff designed for machine reading:\n\n* Newlines in the output are no longer significant\n\n* Each diff unit is prefixed by [-+ ] and terminated by newline as\n  before\n\n* Newlines in the input are output as a line consisting only of a\n  tilde '~'\n\nNote that for backwards compatibility --color-words still implies\n--color, so the order matters: if you say --porcelain --color-words,\nthe hunk headers will be colored; if you say --color-words\n--porcelain, they won't.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n color.c |   28 -----------------\n color.h |    1 -\n diff.c  |  105 ++++++++++++++++++++++++++++++++++++++++++++++++++++----------\n diff.h  |    1 +\n 4 files changed, 89 insertions(+), 46 deletions(-)\n\ndiff --git a/color.c b/color.c\nindex bcf4e2c..1b00554 100644\n--- a/color.c\n+++ b/color.c\n@@ -211,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)\n \tva_end(args);\n \treturn r;\n }\n-\n-/*\n- * This function splits the buffer by newlines and colors the lines individually.\n- *\n- * Returns 0 on success.\n- */\n-int color_fwrite_lines(FILE *fp, const char *color,\n-\t\tsize_t count, const char *buf)\n-{\n-\tif (!*color)\n-\t\treturn fwrite(buf, count, 1, fp) != 1;\n-\twhile (count) {\n-\t\tchar *p = memchr(buf, '\\n', count);\n-\t\tif (p != buf && (fputs(color, fp) < 0 ||\n-\t\t\t\tfwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n-\t\t\t\tfputs(GIT_COLOR_RESET, fp) < 0))\n-\t\t\treturn -1;\n-\t\tif (!p)\n-\t\t\treturn 0;\n-\t\tif (fputc('\\n', fp) < 0)\n-\t\t\treturn -1;\n-\t\tcount -= p + 1 - buf;\n-\t\tbuf = p + 1;\n-\t}\n-\treturn 0;\n-}\n-\n-\ndiff --git a/color.h b/color.h\nindex 5c264b0..03ca064 100644\n--- a/color.h\n+++ b/color.h\n@@ -61,6 +61,5 @@\n int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n __attribute__((format (printf, 3, 4)))\n int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n-int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);\n \n #endif /* COLOR_H */\ndiff --git a/diff.c b/diff.c\nindex f5d93e9..1b4d620 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -577,8 +577,62 @@ struct diff_words_data {\n \tconst char *current_plus;\n \tFILE *file;\n \tregex_t *word_regex;\n+\tint porcelain;\n };\n \n+int color_fwrite_lines(FILE *fp, const char *color,\n+\t\tsize_t count, const char *buf)\n+{\n+\tif (!*color)\n+\t\treturn fwrite(buf, count, 1, fp) != 1;\n+\twhile (count) {\n+\t\tchar *p = memchr(buf, '\\n', count);\n+\t\tif (p != buf && (fputs(color, fp) < 0 ||\n+\t\t\t\tfwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n+\t\t\t\tfputs(GIT_COLOR_RESET, fp) < 0))\n+\t\t\treturn -1;\n+\t\tif (!p)\n+\t\t\treturn 0;\n+\t\tif (fputc('\\n', fp) < 0)\n+\t\t\treturn -1;\n+\t\tcount -= p + 1 - buf;\n+\t\tbuf = p + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int fn_out_diff_words_write_helper(FILE *fp,\n+\t\t\t\t\t   const char *color, const char *prefix,\n+\t\t\t\t\t   size_t count, const char *buf,\n+\t\t\t\t\t   int porcelain)\n+{\n+\tconst char *suffix = \"\\n\";\n+\tconst char *newline = \"~\\n\";\n+\n+\tif (!porcelain) {\n+\t\tif (!color)\n+\t\t\treturn fwrite(buf, count, 1, fp);\n+\t\tprefix = color;\n+\t\tsuffix = GIT_COLOR_RESET;\n+\t\tnewline = \"\\n\";\n+\t}\n+\n+\twhile (count) {\n+\t\tchar *p = memchr(buf, '\\n', count);\n+\t\tif (p != buf && (fputs(prefix, fp) < 0 ||\n+\t\t\t\t fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n+\t\t\t\t fputs(suffix, fp) < 0))\n+\t\t\treturn -1;\n+\t\tif (!p)\n+\t\t\treturn 0;\n+\t\tif (fputs(newline, fp) < 0)\n+\t\t\treturn -1;\n+\t\tcount -= p + 1 - buf;\n+\t\tbuf = p + 1;\n+\t}\n+\treturn 0;\n+}\n+\n static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n {\n \tstruct diff_words_data *diff_words = priv;\n@@ -605,17 +659,21 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n \t\tplus_begin = plus_end = diff_words->plus.orig[plus_first].end;\n \n \tif (diff_words->current_plus != plus_begin)\n-\t\tfwrite(diff_words->current_plus,\n-\t\t\t\tplus_begin - diff_words->current_plus, 1,\n-\t\t\t\tdiff_words->file);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tNULL, \" \",\n+\t\t\t\tplus_begin - diff_words->current_plus,\n+\t\t\t\tdiff_words->current_plus,\n+\t\t\t\tdiff_words->porcelain);\n \tif (minus_begin != minus_end)\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\t\tdiff_get_color(1, DIFF_FILE_OLD),\n-\t\t\t\tminus_end - minus_begin, minus_begin);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tdiff_get_color(1, DIFF_FILE_OLD), \"-\",\n+\t\t\t\tminus_end - minus_begin, minus_begin,\n+\t\t\t\tdiff_words->porcelain);\n \tif (plus_begin != plus_end)\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\t\tdiff_get_color(1, DIFF_FILE_NEW),\n-\t\t\t\tplus_end - plus_begin, plus_begin);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tdiff_get_color(1, DIFF_FILE_NEW), \"+\",\n+\t\t\t\tplus_end - plus_begin, plus_begin,\n+\t\t\t\tdiff_words->porcelain);\n \n \tdiff_words->current_plus = plus_end;\n }\n@@ -700,9 +758,10 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \n \t/* special case: only removal */\n \tif (!diff_words->plus.text.size) {\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\tdiff_get_color(1, DIFF_FILE_OLD),\n-\t\t\tdiff_words->minus.text.size, diff_words->minus.text.ptr);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\tdiff_get_color(1, DIFF_FILE_OLD), \"-\",\n+\t\t\tdiff_words->minus.text.size, diff_words->minus.text.ptr,\n+\t\t\tdiff_words->porcelain);\n \t\tdiff_words->minus.text.size = 0;\n \t\treturn;\n \t}\n@@ -722,10 +781,11 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \tfree(plus.ptr);\n \tif (diff_words->current_plus != diff_words->plus.text.ptr +\n \t\t\tdiff_words->plus.text.size)\n-\t\tfwrite(diff_words->current_plus,\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\tNULL, \" \",\n \t\t\tdiff_words->plus.text.ptr + diff_words->plus.text.size\n-\t\t\t- diff_words->current_plus, 1,\n-\t\t\tdiff_words->file);\n+\t\t\t- diff_words->current_plus, diff_words->current_plus,\n+\t\t\tdiff_words->porcelain);\n \tdiff_words->minus.text.size = diff_words->plus.text.size = 0;\n }\n \n@@ -837,6 +897,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \n \tif (len < 1) {\n \t\temit_line(ecbdata->file, reset, reset, line, len);\n+\t\tif (ecbdata->diff_words && ecbdata->diff_words->porcelain)\n+\t\t\tfputs(\"~\\n\", ecbdata->file);\n \t\treturn;\n \t}\n \n@@ -851,9 +913,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t\t\treturn;\n \t\t}\n \t\tdiff_words_flush(ecbdata);\n-\t\tline++;\n-\t\tlen--;\n+\t\tif (!ecbdata->diff_words->porcelain) {\n+\t\t\tline++;\n+\t\t\tlen--;\n+\t\t}\n \t\temit_line(ecbdata->file, plain, reset, line, len);\n+\t\tif (ecbdata->diff_words->porcelain)\n+\t\t\tfputs(\"~\\n\", ecbdata->file);\n \t\treturn;\n \t}\n \n@@ -1765,6 +1831,7 @@ static void builtin_diff(const char *name_a,\n \t\t\t\to->word_regex = userdiff_word_regex(two);\n \t\t\tif (!o->word_regex)\n \t\t\t\to->word_regex = diff_word_regex_cfg;\n+\t\t\tecbdata.diff_words->porcelain = DIFF_OPT_TST(o, PORCELAIN);\n \t\t\tif (o->word_regex) {\n \t\t\t\tecbdata.diff_words->word_regex = (regex_t *)\n \t\t\t\t\txmalloc(sizeof(regex_t));\n@@ -2852,6 +2919,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n \t\tDIFF_OPT_SET(options, COLOR_DIFF_WORDS);\n \t\toptions->word_regex = arg + 14;\n \t}\n+\telse if (!strcmp(arg, \"--porcelain\")) {\n+\t\tDIFF_OPT_SET(options, PORCELAIN);\n+\t\tDIFF_OPT_CLR(options, COLOR_DIFF);\n+\t}\n \telse if (!strcmp(arg, \"--exit-code\"))\n \t\tDIFF_OPT_SET(options, EXIT_WITH_STATUS);\n \telse if (!strcmp(arg, \"--quiet\"))\ndiff --git a/diff.h b/diff.h\nindex 6a71013..7fdd251 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -71,6 +71,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,\n #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)\n #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)\n #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)\n+#define DIFF_OPT_PORCELAIN           (1 <<  26)\n \n #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)\n #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)\n-- \n1.7.0.3.522.ga3f6f.dirty\n"},{"id":"138242","messageId":"5850172da86408306dee764013d6a0aeed02e604.1269996525.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":"cover.1269996525.git.trast@student.ethz.ch","subject":"[PATCH 2/2] gitk: add the equivalent of diff --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-03-31T00:52:49Z","receivedAt":"2010-03-31T00:52:49Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Use the newly added 'diff --color-words --porcelain' to teach gitk a\ncolor-words mode, triggered by a checkbox.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n gitk-git/gitk |   29 +++++++++++++++++++++++++++--\n 1 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 1f36a3e..ee6e39c 100644\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -2240,6 +2240,9 @@ proc makewindow {} {\n     ${NS}::checkbutton .bleft.mid.ignspace -text [mc \"Ignore space change\"] \\\n \t-command changeignorespace -variable ignorespace\n     pack .bleft.mid.ignspace -side left -padx 5\n+    ${NS}::checkbutton .bleft.mid.worddiff -text [mc \"Word diff\"] \\\n+\t-command changeworddiff -variable worddiff\n+    pack .bleft.mid.worddiff -side left -padx 5\n     set ctext .bleft.bottom.ctext\n     text $ctext -background $bgcolor -foreground $fgcolor \\\n \t-state disabled -font textfont \\\n@@ -7494,11 +7497,16 @@ proc changeignorespace {} {\n     reselectline\n }\n \n+proc changeworddiff {} {\n+    reselectline\n+}\n+\n proc getblobdiffs {ids} {\n     global blobdifffd diffids env\n     global diffinhdr treediffs\n     global diffcontext\n     global ignorespace\n+    global worddiff\n     global limitdiffs vfilelimit curview\n     global diffencoding targetline diffnparents\n     global git_version\n@@ -7515,6 +7523,9 @@ proc getblobdiffs {ids} {\n     if {$ignorespace} {\n \tappend cmd \" -w\"\n     }\n+    if {$worddiff} {\n+\tappend cmd \" --color-words --porcelain\"\n+    }\n     if {$limitdiffs && $vfilelimit($curview) ne {}} {\n \tset cmd [concat $cmd -- $vfilelimit($curview)]\n     }\n@@ -7599,6 +7610,7 @@ proc getblobdiffline {bdf ids} {\n     global ctext_file_names ctext_file_lines\n     global diffinhdr treediffs mergemax diffnparents\n     global diffencoding jump_to_here targetline diffline\n+    global worddiff\n \n     set nr 0\n     $ctext conf -state normal\n@@ -7727,15 +7739,22 @@ proc getblobdiffline {bdf ids} {\n \t    # parse the prefix - one ' ', '-' or '+' for each parent\n \t    set prefix [string range $line 0 [expr {$diffnparents - 1}]]\n \t    set tag [expr {$diffnparents > 1? \"m\": \"d\"}]\n+\t    puts \"prefix is '$prefix'\"\n \t    if {[string trim $prefix \" -+\"] eq {}} {\n \t\t# prefix only has \" \", \"-\" and \"+\" in it: normal diff line\n \t\tset num [string first \"-\" $prefix]\n+\t\tif {$worddiff} {\n+\t\t    set line [string range $line 1 end]\n+\t\t}\n \t\tif {$num >= 0} {\n \t\t    # removed line, first parent with line is $num\n \t\t    if {$num >= $mergemax} {\n \t\t\tset num \"max\"\n \t\t    }\n-\t\t    $ctext insert end \"$line\\n\" $tag$num\n+\t\t    $ctext insert end \"$line\" $tag$num\n+\t\t    if {!$worddiff} {\n+\t\t\t$ctext insert end \"\\n\" $tag$num\n+\t\t    }\n \t\t} else {\n \t\t    set tags {}\n \t\t    if {[string first \"+\" $prefix] >= 0} {\n@@ -7759,8 +7778,13 @@ proc getblobdiffline {bdf ids} {\n \t\t\t    incr diffline\n \t\t\t}\n \t\t    }\n-\t\t    $ctext insert end \"$line\\n\" $tags\n+\t\t    $ctext insert end \"$line\" $tags\n+\t\t    if {!$worddiff} {\n+\t\t\t$ctext insert end \"\\n\" $tags\n+\t\t    }\n \t\t}\n+\t    } elseif {$worddiff && $prefix eq \"~\"} {\n+\t\t$ctext insert end \"\\n\" {}\n \t    } else {\n \t\t# \"\\ No newline at end of file\",\n \t\t# or something else we don't recognize\n@@ -11379,6 +11403,7 @@ if {[tk windowingsystem] eq \"win32\"} {\n set diffcolors {red \"#00a000\" blue}\n set diffcontext 3\n set ignorespace 0\n+set worddiff 0\n set markbgcolor \"#e0e0ff\"\n \n set circlecolors {white blue gray blue blue}\n-- \n1.7.0.3.522.ga3f6f.dirty\n"},{"id":"138279","messageId":"201003311404.17760.trast@student.ethz.ch","threadId":"23264","inReplyTo":"5850172da86408306dee764013d6a0aeed02e604.1269996525.git.trast@student.ethz.ch","subject":"Re: [PATCH 2/2] gitk: add the equivalent of diff --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-03-31T12:04:17Z","receivedAt":"2010-03-31T12:04:17Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Thomas Rast wrote:\n> @@ -7727,15 +7739,22 @@ proc getblobdiffline {bdf ids} {\n>  \t    # parse the prefix - one ' ', '-' or '+' for each parent\n>  \t    set prefix [string range $line 0 [expr {$diffnparents - 1}]]\n>  \t    set tag [expr {$diffnparents > 1? \"m\": \"d\"}]\n> +\t    puts \"prefix is '$prefix'\"\n\nThis is a leftover debugging print and should be removed if you want\nto try it... sorry for the noise.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"},{"id":"138501","messageId":"cover.1270317502.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":"cover.1269996525.git.trast@student.ethz.ch","subject":"[PATCH v2 0/2] gitk --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-04-03T18:06:18Z","receivedAt":"2010-04-03T18:06:18Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"So here's a version intended for application.\n\n[1/2] gained tests and docs.\n\n[2/2] now disables the word-diff code for merge commits, since the\nunderlying --color-words also just gives up and shows a combined\ndiff.  It also patches /gitk, i.e., it's intended for application\ninside the subtree.\n\nI wasn't really sure about the last point -- this makes things a bit\ncomplicated since Paul will have to apply 2/2, and Junio will have to\ntake care to only pull from Paul once 1/2 is in.\n\n\nThomas Rast (2):\n  Add diff --porcelain option for --color-words\n  gitk: add the equivalent of diff --color-words\n"},{"id":"138503","messageId":"f5741e49b2c09ec76def2d21a84a7da6ff7b8368.1270317502.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":"cover.1270317502.git.trast@student.ethz.ch","subject":"[PATCH v2 1/2] Add diff --porcelain option for --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-04-03T18:06:19Z","receivedAt":"2010-04-03T18:06:19Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"This teaches the --color-words mode a new option --porcelain that\ndisables color mode again and instead uses an ad-hoc format for the\nword diff designed for machine reading:\n\n* Newlines in the output are no longer significant\n\n* Each diff unit is prefixed by [-+ ] and terminated by newline as\n  before\n\n* Newlines in the input are output as a line consisting only of a\n  tilde '~'\n\nNote that for backwards compatibility --color-words still implies\n--color, so the order matters: if you say --porcelain --color-words,\nthe hunk headers will be colored; if you say --color-words\n--porcelain, they won't.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n Documentation/diff-options.txt |   11 ++++\n color.c                        |   28 -----------\n color.h                        |    1 -\n diff.c                         |  105 +++++++++++++++++++++++++++++++++-------\n diff.h                         |    1 +\n t/t4034-diff-words.sh          |   27 ++++++++++\n 6 files changed, 127 insertions(+), 46 deletions(-)\n\ndiff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt\nindex 60e922e..ca9c7ad 100644\n--- a/Documentation/diff-options.txt\n+++ b/Documentation/diff-options.txt\n@@ -142,6 +142,17 @@ The regex can also be set via a diff driver or configuration option, see\n linkgit:gitattributes[1] or linkgit:git-config[1].  Giving it explicitly\n overrides any diff driver or configuration setting.  Diff drivers\n override configuration settings.\n++\n+With `--porcelain`, use a variant of the unified diff format:\n+Added/removed/unchanged runs of words are printed as a line starting\n+with `+`/`-`/` ` (respectively) and terminated by a newline.  Newlines\n+in the input are shown as a tilde `~` on a line of its own.\n+\n+--porcelain::\n+\tSwitch to plumbing mode, designed for consumption by a\n+\tporcelain script.  Currently, this only affects\n+\t`--color-words`, which see.\n+\n endif::git-format-patch[]\n \n --no-renames::\ndiff --git a/color.c b/color.c\nindex bcf4e2c..1b00554 100644\n--- a/color.c\n+++ b/color.c\n@@ -211,31 +211,3 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)\n \tva_end(args);\n \treturn r;\n }\n-\n-/*\n- * This function splits the buffer by newlines and colors the lines individually.\n- *\n- * Returns 0 on success.\n- */\n-int color_fwrite_lines(FILE *fp, const char *color,\n-\t\tsize_t count, const char *buf)\n-{\n-\tif (!*color)\n-\t\treturn fwrite(buf, count, 1, fp) != 1;\n-\twhile (count) {\n-\t\tchar *p = memchr(buf, '\\n', count);\n-\t\tif (p != buf && (fputs(color, fp) < 0 ||\n-\t\t\t\tfwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n-\t\t\t\tfputs(GIT_COLOR_RESET, fp) < 0))\n-\t\t\treturn -1;\n-\t\tif (!p)\n-\t\t\treturn 0;\n-\t\tif (fputc('\\n', fp) < 0)\n-\t\t\treturn -1;\n-\t\tcount -= p + 1 - buf;\n-\t\tbuf = p + 1;\n-\t}\n-\treturn 0;\n-}\n-\n-\ndiff --git a/color.h b/color.h\nindex 5c264b0..03ca064 100644\n--- a/color.h\n+++ b/color.h\n@@ -61,6 +61,5 @@\n int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);\n __attribute__((format (printf, 3, 4)))\n int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);\n-int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);\n \n #endif /* COLOR_H */\ndiff --git a/diff.c b/diff.c\nindex f5d93e9..1b4d620 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -577,8 +577,62 @@ struct diff_words_data {\n \tconst char *current_plus;\n \tFILE *file;\n \tregex_t *word_regex;\n+\tint porcelain;\n };\n \n+int color_fwrite_lines(FILE *fp, const char *color,\n+\t\tsize_t count, const char *buf)\n+{\n+\tif (!*color)\n+\t\treturn fwrite(buf, count, 1, fp) != 1;\n+\twhile (count) {\n+\t\tchar *p = memchr(buf, '\\n', count);\n+\t\tif (p != buf && (fputs(color, fp) < 0 ||\n+\t\t\t\tfwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n+\t\t\t\tfputs(GIT_COLOR_RESET, fp) < 0))\n+\t\t\treturn -1;\n+\t\tif (!p)\n+\t\t\treturn 0;\n+\t\tif (fputc('\\n', fp) < 0)\n+\t\t\treturn -1;\n+\t\tcount -= p + 1 - buf;\n+\t\tbuf = p + 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static int fn_out_diff_words_write_helper(FILE *fp,\n+\t\t\t\t\t   const char *color, const char *prefix,\n+\t\t\t\t\t   size_t count, const char *buf,\n+\t\t\t\t\t   int porcelain)\n+{\n+\tconst char *suffix = \"\\n\";\n+\tconst char *newline = \"~\\n\";\n+\n+\tif (!porcelain) {\n+\t\tif (!color)\n+\t\t\treturn fwrite(buf, count, 1, fp);\n+\t\tprefix = color;\n+\t\tsuffix = GIT_COLOR_RESET;\n+\t\tnewline = \"\\n\";\n+\t}\n+\n+\twhile (count) {\n+\t\tchar *p = memchr(buf, '\\n', count);\n+\t\tif (p != buf && (fputs(prefix, fp) < 0 ||\n+\t\t\t\t fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||\n+\t\t\t\t fputs(suffix, fp) < 0))\n+\t\t\treturn -1;\n+\t\tif (!p)\n+\t\t\treturn 0;\n+\t\tif (fputs(newline, fp) < 0)\n+\t\t\treturn -1;\n+\t\tcount -= p + 1 - buf;\n+\t\tbuf = p + 1;\n+\t}\n+\treturn 0;\n+}\n+\n static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n {\n \tstruct diff_words_data *diff_words = priv;\n@@ -605,17 +659,21 @@ static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len)\n \t\tplus_begin = plus_end = diff_words->plus.orig[plus_first].end;\n \n \tif (diff_words->current_plus != plus_begin)\n-\t\tfwrite(diff_words->current_plus,\n-\t\t\t\tplus_begin - diff_words->current_plus, 1,\n-\t\t\t\tdiff_words->file);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tNULL, \" \",\n+\t\t\t\tplus_begin - diff_words->current_plus,\n+\t\t\t\tdiff_words->current_plus,\n+\t\t\t\tdiff_words->porcelain);\n \tif (minus_begin != minus_end)\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\t\tdiff_get_color(1, DIFF_FILE_OLD),\n-\t\t\t\tminus_end - minus_begin, minus_begin);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tdiff_get_color(1, DIFF_FILE_OLD), \"-\",\n+\t\t\t\tminus_end - minus_begin, minus_begin,\n+\t\t\t\tdiff_words->porcelain);\n \tif (plus_begin != plus_end)\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\t\tdiff_get_color(1, DIFF_FILE_NEW),\n-\t\t\t\tplus_end - plus_begin, plus_begin);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\t\tdiff_get_color(1, DIFF_FILE_NEW), \"+\",\n+\t\t\t\tplus_end - plus_begin, plus_begin,\n+\t\t\t\tdiff_words->porcelain);\n \n \tdiff_words->current_plus = plus_end;\n }\n@@ -700,9 +758,10 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \n \t/* special case: only removal */\n \tif (!diff_words->plus.text.size) {\n-\t\tcolor_fwrite_lines(diff_words->file,\n-\t\t\tdiff_get_color(1, DIFF_FILE_OLD),\n-\t\t\tdiff_words->minus.text.size, diff_words->minus.text.ptr);\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\tdiff_get_color(1, DIFF_FILE_OLD), \"-\",\n+\t\t\tdiff_words->minus.text.size, diff_words->minus.text.ptr,\n+\t\t\tdiff_words->porcelain);\n \t\tdiff_words->minus.text.size = 0;\n \t\treturn;\n \t}\n@@ -722,10 +781,11 @@ static void diff_words_show(struct diff_words_data *diff_words)\n \tfree(plus.ptr);\n \tif (diff_words->current_plus != diff_words->plus.text.ptr +\n \t\t\tdiff_words->plus.text.size)\n-\t\tfwrite(diff_words->current_plus,\n+\t\tfn_out_diff_words_write_helper(diff_words->file,\n+\t\t\tNULL, \" \",\n \t\t\tdiff_words->plus.text.ptr + diff_words->plus.text.size\n-\t\t\t- diff_words->current_plus, 1,\n-\t\t\tdiff_words->file);\n+\t\t\t- diff_words->current_plus, diff_words->current_plus,\n+\t\t\tdiff_words->porcelain);\n \tdiff_words->minus.text.size = diff_words->plus.text.size = 0;\n }\n \n@@ -837,6 +897,8 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \n \tif (len < 1) {\n \t\temit_line(ecbdata->file, reset, reset, line, len);\n+\t\tif (ecbdata->diff_words && ecbdata->diff_words->porcelain)\n+\t\t\tfputs(\"~\\n\", ecbdata->file);\n \t\treturn;\n \t}\n \n@@ -851,9 +913,13 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)\n \t\t\treturn;\n \t\t}\n \t\tdiff_words_flush(ecbdata);\n-\t\tline++;\n-\t\tlen--;\n+\t\tif (!ecbdata->diff_words->porcelain) {\n+\t\t\tline++;\n+\t\t\tlen--;\n+\t\t}\n \t\temit_line(ecbdata->file, plain, reset, line, len);\n+\t\tif (ecbdata->diff_words->porcelain)\n+\t\t\tfputs(\"~\\n\", ecbdata->file);\n \t\treturn;\n \t}\n \n@@ -1765,6 +1831,7 @@ static void builtin_diff(const char *name_a,\n \t\t\t\to->word_regex = userdiff_word_regex(two);\n \t\t\tif (!o->word_regex)\n \t\t\t\to->word_regex = diff_word_regex_cfg;\n+\t\t\tecbdata.diff_words->porcelain = DIFF_OPT_TST(o, PORCELAIN);\n \t\t\tif (o->word_regex) {\n \t\t\t\tecbdata.diff_words->word_regex = (regex_t *)\n \t\t\t\t\txmalloc(sizeof(regex_t));\n@@ -2852,6 +2919,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)\n \t\tDIFF_OPT_SET(options, COLOR_DIFF_WORDS);\n \t\toptions->word_regex = arg + 14;\n \t}\n+\telse if (!strcmp(arg, \"--porcelain\")) {\n+\t\tDIFF_OPT_SET(options, PORCELAIN);\n+\t\tDIFF_OPT_CLR(options, COLOR_DIFF);\n+\t}\n \telse if (!strcmp(arg, \"--exit-code\"))\n \t\tDIFF_OPT_SET(options, EXIT_WITH_STATUS);\n \telse if (!strcmp(arg, \"--quiet\"))\ndiff --git a/diff.h b/diff.h\nindex 6a71013..7fdd251 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -71,6 +71,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,\n #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)\n #define DIFF_OPT_DIRTY_SUBMODULES    (1 << 24)\n #define DIFF_OPT_IGNORE_UNTRACKED_IN_SUBMODULES (1 << 25)\n+#define DIFF_OPT_PORCELAIN           (1 <<  26)\n \n #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)\n #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex 2e2e103..26637d3 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -55,6 +55,33 @@ test_expect_success 'word diff with runs of whitespace' '\n \n '\n \n+sed 's/#.*$//' > expect <<EOF\n+diff --git a/pre b/post\n+index 330b04f..5ed8eff 100644\n+--- a/pre\n++++ b/post\n+@@ -1,3 +1,7 @@\n+-h(4)\n++h(4),hh[44]\n+~\n+ # significant space\n+~\n+ a = b + c\n+~\n+~\n++aa = a\n+~\n+~\n++aeff = aeff * ( aaa )\n+~\n+EOF\n+\n+test_expect_success '--porcelain' '\n+\n+\tword_diff --color-words --porcelain\n+\n+'\n+\n cat > expect <<\\EOF\n <WHITE>diff --git a/pre b/post<RESET>\n <WHITE>index 330b04f..5ed8eff 100644<RESET>\n-- \n1.7.0.4.504.g4d2c\n"},{"id":"138502","messageId":"057c2dc29e3392103470ecf782f92d3cd432ded6.1270317502.git.trast@student.ethz.ch","threadId":"23264","inReplyTo":"cover.1270317502.git.trast@student.ethz.ch","subject":"[PATCH v2 2/2] gitk: add the equivalent of diff --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-04-03T18:06:20Z","receivedAt":"2010-04-03T18:06:20Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Use the newly added 'diff --color-words --porcelain' to teach gitk a\ncolor-words mode, triggered by a checkbox.\n\nSigned-off-by: Thomas Rast <trast@student.ethz.ch>\n---\n gitk |   29 +++++++++++++++++++++++++++--\n 1 files changed, 27 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk b/gitk\nindex 1f36a3e..d269226 100755\n--- a/gitk\n+++ b/gitk\n@@ -2240,6 +2240,9 @@ proc makewindow {} {\n     ${NS}::checkbutton .bleft.mid.ignspace -text [mc \"Ignore space change\"] \\\n \t-command changeignorespace -variable ignorespace\n     pack .bleft.mid.ignspace -side left -padx 5\n+    ${NS}::checkbutton .bleft.mid.worddiff -text [mc \"Word diff\"] \\\n+\t-command changeworddiff -variable worddiff\n+    pack .bleft.mid.worddiff -side left -padx 5\n     set ctext .bleft.bottom.ctext\n     text $ctext -background $bgcolor -foreground $fgcolor \\\n \t-state disabled -font textfont \\\n@@ -7494,11 +7497,16 @@ proc changeignorespace {} {\n     reselectline\n }\n \n+proc changeworddiff {} {\n+    reselectline\n+}\n+\n proc getblobdiffs {ids} {\n     global blobdifffd diffids env\n     global diffinhdr treediffs\n     global diffcontext\n     global ignorespace\n+    global worddiff\n     global limitdiffs vfilelimit curview\n     global diffencoding targetline diffnparents\n     global git_version\n@@ -7515,6 +7523,9 @@ proc getblobdiffs {ids} {\n     if {$ignorespace} {\n \tappend cmd \" -w\"\n     }\n+    if {$worddiff} {\n+\tappend cmd \" --color-words --porcelain\"\n+    }\n     if {$limitdiffs && $vfilelimit($curview) ne {}} {\n \tset cmd [concat $cmd -- $vfilelimit($curview)]\n     }\n@@ -7599,6 +7610,7 @@ proc getblobdiffline {bdf ids} {\n     global ctext_file_names ctext_file_lines\n     global diffinhdr treediffs mergemax diffnparents\n     global diffencoding jump_to_here targetline diffline\n+    global worddiff\n \n     set nr 0\n     $ctext conf -state normal\n@@ -7727,15 +7739,22 @@ proc getblobdiffline {bdf ids} {\n \t    # parse the prefix - one ' ', '-' or '+' for each parent\n \t    set prefix [string range $line 0 [expr {$diffnparents - 1}]]\n \t    set tag [expr {$diffnparents > 1? \"m\": \"d\"}]\n+\t    set dowords [expr {$worddiff && $diffnparents == 1}]\n \t    if {[string trim $prefix \" -+\"] eq {}} {\n \t\t# prefix only has \" \", \"-\" and \"+\" in it: normal diff line\n \t\tset num [string first \"-\" $prefix]\n+\t\tif {$dowords} {\n+\t\t    set line [string range $line 1 end]\n+\t\t}\n \t\tif {$num >= 0} {\n \t\t    # removed line, first parent with line is $num\n \t\t    if {$num >= $mergemax} {\n \t\t\tset num \"max\"\n \t\t    }\n-\t\t    $ctext insert end \"$line\\n\" $tag$num\n+\t\t    $ctext insert end \"$line\" $tag$num\n+\t\t    if {!$dowords} {\n+\t\t\t$ctext insert end \"\\n\" $tag$num\n+\t\t    }\n \t\t} else {\n \t\t    set tags {}\n \t\t    if {[string first \"+\" $prefix] >= 0} {\n@@ -7759,8 +7778,13 @@ proc getblobdiffline {bdf ids} {\n \t\t\t    incr diffline\n \t\t\t}\n \t\t    }\n-\t\t    $ctext insert end \"$line\\n\" $tags\n+\t\t    $ctext insert end \"$line\" $tags\n+\t\t    if {!$dowords} {\n+\t\t\t$ctext insert end \"\\n\" $tags\n+\t\t    }\n \t\t}\n+\t    } elseif {$dowords && $prefix eq \"~\"} {\n+\t\t$ctext insert end \"\\n\" {}\n \t    } else {\n \t\t# \"\\ No newline at end of file\",\n \t\t# or something else we don't recognize\n@@ -11379,6 +11403,7 @@ if {[tk windowingsystem] eq \"win32\"} {\n set diffcolors {red \"#00a000\" blue}\n set diffcontext 3\n set ignorespace 0\n+set worddiff 0\n set markbgcolor \"#e0e0ff\"\n \n set circlecolors {white blue gray blue blue}\n-- \n1.7.0.4.504.g4d2c\n"},{"id":"138506","messageId":"7vmxxk8i01.fsf@alter.siamese.dyndns.org","threadId":"23264","inReplyTo":"f5741e49b2c09ec76def2d21a84a7da6ff7b8368.1270317502.git.trast@student.ethz.ch","subject":"Re: [PATCH v2 1/2] Add diff --porcelain option for --color-words","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-04-03T19:15:42Z","receivedAt":"2010-04-03T19:15:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Thomas Rast <trast@student.ethz.ch> writes:\n\n> This teaches the --color-words mode a new option --porcelain that\n> disables color mode again and instead uses an ad-hoc format for the\n> word diff designed for machine reading.\n\nHow readable can you make this for human consumption while still keeping\nit machine readable?  The answer could be it already is human readble.\n\nTwo reasons I ask the above question are that I find the feature quite\ninteresting, and would want to see if it can be also fed to humans, and\nthat the combination of this new option and the existing --color-words is\nmisnamed.\n\nWhat you are giving \"git diff\" is \"word-level diff\" as opposed to the\nusual \"line-level diff\", isn't it?  The machinery may have already been\nthere, but it had a hardwired representation of the result to color pre-\nand post- image words, and you are giving the result from the machinery\nanother representation with this patch.  If you call this --word-diff,\nthen it would become more clear that --color-words perhaps should have\nbeen called --word-diff=color or something.\n\nBesides, --porcelain invites \"what does it do without --color-words?\", to\nwhich you wouldn't have a good answer, as non word-diff output is already\nmachine readable.\n"},{"id":"138517","messageId":"87wrwoma9x.fsf@catnip.gol.com","threadId":"23264","inReplyTo":"7vmxxk8i01.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v2 1/2] Add diff --porcelain option for --color-words","fromName":"Miles Bader","fromEmail":"miles@gnu.org","sentAt":"2010-04-03T22:38:50Z","receivedAt":"2010-04-03T22:38:50Z","isPatch":true,"sender":{"key":"miles@gnu.org","avatar":"https://gravatar.com/avatar/01069b69593af7bff28e2f97afeb3644ae6fe2f5f56cb3a8cf34c5fb8c36efe5?d=mp&s=160"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n> How readable can you make this for human consumption while still keeping\n> it machine readable?  The answer could be it already is human readble.\n>\n> Two reasons I ask the above question are that I find the feature quite\n> interesting, and would want to see if it can be also fed to humans, and\n> that the combination of this new option and the existing --color-words is\n> misnamed.\n\nThere's the format used by the \"wdiff\" program, which is more like\ntraditional diff output in that it doesn't use color, but is human\nfriendly, and also seems to be somewhat machine-parseable:\n\n   $ echo 'This is a test' > /tmp/a\n   $ echo 'This is funky test' > /tmp/b\n   $ wdiff /tmp/a /tmp/b\n   This is [-a-] {+funky+} test\n\n[I say \"somewhat\" because wdiff itself doesn't appear to escape potentially\nambiguous content, e.g., if there's actually a \"{+\" in the file....]\n\n-Miles\n\n-- \nMarriage, n. The state or condition of a community consisting of a master, a\nmistress and two slaves, making in all, two.\n"},{"id":"138583","messageId":"20100405064018.GA4471@brick.ozlabs.ibm.com","threadId":"23264","inReplyTo":"cover.1270317502.git.trast@student.ethz.ch","subject":"Re: [PATCH v2 0/2] gitk --color-words","fromName":"Paul Mackerras","fromEmail":"paulus@samba.org","sentAt":"2010-04-05T06:40:18Z","receivedAt":"2010-04-05T06:40:18Z","isPatch":true,"sender":{"key":"paulus@samba.org","avatar":"https://avatars.githubusercontent.com/u/1606439?v=4"},"body":"On Sat, Apr 03, 2010 at 08:06:18PM +0200, Thomas Rast wrote:\n\n> I wasn't really sure about the last point -- this makes things a bit\n> complicated since Paul will have to apply 2/2, and Junio will have to\n> take care to only pull from Paul once 1/2 is in.\n\nYou mean that the patch will mean that gitk will break if the\nunderlying git isn't new enough?  I have been careful to avoid that\nsort of dependency as far as possible.  Have a look at how this is\nhandled for the --textconv and --submodule options in [getblobdiffs]\nand do something similar for --color-words, please.\n\nPaul.\n"},{"id":"138586","messageId":"201004051221.37965.trast@student.ethz.ch","threadId":"23264","inReplyTo":"20100405064018.GA4471@brick.ozlabs.ibm.com","subject":"Re: [PATCH v2 0/2] gitk --color-words","fromName":"Thomas Rast","fromEmail":"trast@student.ethz.ch","sentAt":"2010-04-05T10:21:37Z","receivedAt":"2010-04-05T10:21:37Z","isPatch":true,"sender":{"key":"tr@thomasrast.ch","avatar":"https://avatars.githubusercontent.com/u/153510?v=4"},"body":"Paul Mackerras wrote:\n> On Sat, Apr 03, 2010 at 08:06:18PM +0200, Thomas Rast wrote:\n> \n> > I wasn't really sure about the last point -- this makes things a bit\n> > complicated since Paul will have to apply 2/2, and Junio will have to\n> > take care to only pull from Paul once 1/2 is in.\n> \n> You mean that the patch will mean that gitk will break if the\n> underlying git isn't new enough?  I have been careful to avoid that\n> sort of dependency as far as possible.  Have a look at how this is\n> handled for the --textconv and --submodule options in [getblobdiffs]\n> and do something similar for --color-words, please.\n\nOk, I'll look into that.\n\n-- \nThomas Rast\ntrast@{inf,student}.ethz.ch\n"}]}