threads / patch / 30059

patch, 4 partsgrep: move code to print hunk markers after heading

Subject: [PATCH 3/4] grep: move code to print hunk markers after heading

## tl;dr

15 messages between Mar 26, 2012 and Mar 27, 2012. Diffs are folded; open one to read it.

replies: 14people: 4as markdown or json

Mark Lodato· Mar 26, 2012, 02:41 UTC · lore

[PATCH 0/4] grep: add more information to hunk separators

This patch series adds a new `grep --hunk-heading' option that moves the filename and line number to the hunk separator lines ("--") rather than at the beginning of each matching (or context) line. In my opinion, this makes the output easier to read, especially when combined with `--heading'.

I am not sure that "hunk-heading" is the best term, so I welcome ideas on better names.

Here's an example:
    # Current behavior:
    $ git grep -p -C1 -n list_common -- git.c
    git.c=531=int main(int argc, const char **argv)
    --
    git.c-570-              printf("usage: %s\n\n", git_usage_string);
    git.c:571:              list_common_cmds_help();
    git.c-572-              printf("\n%s\n", git_more_info_string);
    # New option:
    $ git grep -p -C1 --hunk-heading list_common -- git.c
    -- git.c:531 --
    int main(int argc, char argv)
    -- git.c:570 --
                    printf("usage: %s\n\n", git_usage_string);
                    list_common_cmds_help();
                    printf("\n%s\n", git_more_info_string);
    # New option with --heading:
    $ git grep -p -C1 --hunk-heading --heading list_common -- git.c
    git.c
    -- 531 --
    int main(int argc, char argv)
    -- 570 --
                    printf("usage: %s\n\n", git_usage_string);
                    list_common_cmds_help();
                    printf("\n%s\n", git_more_info_string);

Originally, I had envisioned also moving the function name (`-p') to the hunk header, similar to the diff context line. For example:

    -- git.c:570 -- int main(int argc, char argv)
                    printf("usage: %s\n\n", git_usage_string);
                    list_common_cmds_help();
                    printf("\n%s\n", git_more_info_string);

After implementing this feature, I was not happy with the result and subsequently removed it. To me, the output was too cluttered and the line number was ambigous. For example, in the above, it is not obvious to me that line 570 is the "printf" line and not the "int main" line. Still, if you would like to see the patch to implement this feature, please let me know.

Mark Lodato (4):
  grep doc: add --break / --heading / -W to synopsis
  add tests for grep --heading with context
  grep: move code to print hunk markers after heading
  grep: add --hunk-heading option
 Documentation/config.txt     |    3 +
 Documentation/git-grep.txt   |   10 +++
 builtin/grep.c               |   10 ++-
 grep.c                       |   49 ++++++++----
 grep.h                       |    1 +
 t/t7810-grep.sh              |   37 +++++++++
 t/t7812-grep-hunk-heading.sh |  181 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 276 insertions(+), 15 deletions(-)
 create mode 100755 t/t7812-grep-hunk-heading.sh
-- 
1.7.9.2
Mark Lodato· Mar 26, 2012, 02:41 UTC · re: Mark Lodato · lore

[PATCH 1/4] grep doc: add --break / --heading / -W to synopsis

All of the other options were included in the synopsis, so it makes sense to include these as well.

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---
 Documentation/git-grep.txt |    2 ++
 1 file changed, 2 insertions(+)
Show changes to Documentation/git-grep.txt +2 −0
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 6a8b1e3..343eadd 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -20,7 +20,9 @@ SYNOPSIS
 	   [-c | --count] [--all-match] [-q | --quiet]
 	   [--max-depth <depth>]
 	   [--color[=<when>] | --no-color]
+	   [--break] [--heading] [-p | --show-function]
 	   [-A <post-context>] [-B <pre-context>] [-C <context>]
+	   [-W | --function-context]
 	   [-f <file>] [-e] <pattern>
 	   [--and|--or|--not|(|)|-e <pattern>...]
 	   [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
-- 
1.7.9.2
Mark Lodato· Mar 26, 2012, 02:41 UTC · re: Mark Lodato · lore

[PATCH 2/4] add tests for grep --heading with context

It is easy to make a mistake in the logic that conditionally prints out hunk separators in the various combinations of --heading, --break, and --context.

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---
 t/t7810-grep.sh |   37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)
Show changes to t/t7810-grep.sh +37 −0
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index d9ad633..58c0821 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -901,6 +901,43 @@ test_expect_success 'mimic ack-grep --group' '
 '
 
 cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+{
+--
+	/* char ?? */
+}
+--
+hello_world
+Hello_world
+HeLLo_world
+EOF
+
+test_expect_success 'grep --heading with context' '
+	git grep --heading -A1 -e char -e lo_w hello.c hello_world >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+hello.c
+int main(int argc, const char **argv)
+{
+--
+	/* char ?? */
+}
+
+hello_world
+Hello_world
+HeLLo_world
+EOF
+
+test_expect_success 'grep --break --heading with context' '
+	git grep --break --heading -A1 \
+		-e char -e lo_w hello.c hello_world >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
 space: line with leading space1
 space: line with leading space2
 space: line with leading space3
-- 
1.7.9.2
Mark Lodato· Mar 26, 2012, 02:41 UTC · re: Mark Lodato · lore

This commit does not have any effect on the output of the program. The purpose is to distinguish in the code between two kinds of hunk separators: those that occur before the heading and those that occur after. This step is necessary for the next commit, which will modify only the latter tyep of hunk separators.

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---
 grep.c |   25 +++++++++++++------------
 1 file changed, 13 insertions(+), 12 deletions(-)
Show changes to grep.c +13 −12
diff --git a/grep.c b/grep.c
index 190139c..14e0480 100644
--- a/grep.c
+++ b/grep.c
@@ -739,25 +739,26 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
 {
 	int rest = eol - bol;
 	char *line_color = NULL;
+	int show_hunk = (opt->pre_context || opt->post_context ||
+			 opt->funcbody);
 
-	if (opt->file_break && opt->last_shown == 0) {
-		if (opt->show_hunk_mark)
-			opt->output(opt, "\n", 1);
-	} else if (opt->pre_context || opt->post_context || opt->funcbody) {
-		if (opt->last_shown == 0) {
-			if (opt->show_hunk_mark) {
-				output_color(opt, "--", 2, opt->color_sep);
-				opt->output(opt, "\n", 1);
-			}
-		} else if (lno > opt->last_shown + 1) {
+	if (opt->show_hunk_mark && opt->last_shown == 0 &&
+	    (opt->file_break || (opt->heading && show_hunk))) {
+		if (opt->heading && !opt->file_break)
 			output_color(opt, "--", 2, opt->color_sep);
-			opt->output(opt, "\n", 1);
-		}
+		opt->output(opt, "\n", 1);
+		show_hunk = 0;
 	}
 	if (opt->heading && opt->last_shown == 0) {
 		output_color(opt, name, strlen(name), opt->color_filename);
 		opt->output(opt, "\n", 1);
 	}
+	if (show_hunk &&
+	    ((opt->last_shown == 0 && opt->show_hunk_mark) ||
+	     (opt->last_shown != 0 && lno > opt->last_shown + 1))) {
+		output_color(opt, "--", 2, opt->color_sep);
+		opt->output(opt, "\n", 1);
+	}
 	opt->last_shown = lno;
 
 	if (!opt->heading && opt->pathname) {
-- 
1.7.9.2
Mark Lodato· Mar 26, 2012, 02:41 UTC · re: Mark Lodato · lore

[PATCH 4/4] grep: add --hunk-heading option

The purpose of this option is to make the output of grep when showing context (i.e., -A, -B, -C, or -W) easier to read. By default, grep prints a simple separator ("--") between each hunk and then prefixes each line within the hunk with the same filename and optionally an incrementing line number. This repeated information is redundant, so the new --hunk-heading option moves it all to the hunk separator line. The idea is similar to the hunk context line of diff.

The new option can be combined with --heading to provide both the filename and line number of each hunk with minimal visual clutter.

The new configuration, grep.hunkHeading, can be used to set this option by default.

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---
 Documentation/config.txt     |    3 +
 Documentation/git-grep.txt   |   10 ++-
 builtin/grep.c               |   10 ++-
 grep.c                       |   28 ++++++-
 grep.h                       |    1 +
 t/t7812-grep-hunk-heading.sh |  181 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 227 insertions(+), 6 deletions(-)
 create mode 100755 t/t7812-grep-hunk-heading.sh
Show changes to 6 files +227 −6

Documentation/config.txt, Documentation/git-grep.txt, builtin/grep.c, grep.c, grep.h, t/t7812-grep-hunk-heading.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index c081657..ade9503 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1140,6 +1140,9 @@ grep.lineNumber::
 grep.extendedRegexp::
 	If set to true, enable '--extended-regexp' option by default.
 
+grep.hunkHeading::
+	If set to true, enable '--hunk-heading' option by default.
+
 gpg.program::
 	Use this custom program instead of "gpg" found on $PATH when
 	making or verifying a PGP signature. The program must support the
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 343eadd..26c085b 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -20,7 +20,7 @@ SYNOPSIS
 	   [-c | --count] [--all-match] [-q | --quiet]
 	   [--max-depth <depth>]
 	   [--color[=<when>] | --no-color]
-	   [--break] [--heading] [-p | --show-function]
+	   [--break] [--heading] [--hunk-heading] [-p | --show-function]
 	   [-A <post-context>] [-B <pre-context>] [-C <context>]
 	   [-W | --function-context]
 	   [-f <file>] [-e] <pattern>
@@ -43,6 +43,9 @@ grep.lineNumber::
 grep.extendedRegexp::
 	If set to true, enable '--extended-regexp' option by default.
 
+grep.hunkHeading::
+	If set to true, enable '--hunk-heading' option by default.
+
 
 OPTIONS
 -------
@@ -173,6 +176,11 @@ OPTIONS
 	Show the filename above the matches in that file instead of
 	at the start of each shown line.
 
+--hunk-heading::
+	Append the filename (if not `--heading` and not `-h`) and line number
+	(if not `-n`) to each hunk separator, and suppress printing of the
+	filename at the start of each shown line.
+
 -p::
 --show-function::
 	Show the preceding line that contains the function name of
diff --git a/builtin/grep.c b/builtin/grep.c
index 643938d..cdafc5a 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -276,6 +276,11 @@ static int grep_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "grep.hunkheading")) {
+		opt->hunk_heading = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "grep.linenumber")) {
 		opt->linenum = git_config_bool(var, value);
 		return 0;
@@ -740,6 +745,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 			"print empty line between matches from different files"),
 		OPT_BOOLEAN(0, "heading", &opt.heading,
 			"show filename only once above matches from same file"),
+		OPT_BOOLEAN(0, "hunk-heading", &opt.hunk_heading,
+			"show filename and line number after hunk separator"),
 		OPT_GROUP(""),
 		OPT_CALLBACK('C', "context", &opt, "n",
 			"show <n> context lines before and after matches",
@@ -920,8 +927,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
 #ifndef NO_PTHREADS
 	if (use_threads) {
 		if (!(opt.name_only || opt.unmatch_name_only || opt.count)
+		    && !(opt.hunk_heading && !opt.heading && !opt.file_break)
 		    && (opt.pre_context || opt.post_context ||
-			opt.file_break || opt.funcbody))
+			opt.file_break || opt.funcbody || opt.hunk_heading))
 			skip_first_line = 1;
 		start_threads(&opt);
 	}
diff --git a/grep.c b/grep.c
index 14e0480..f0e00f7 100644
--- a/grep.c
+++ b/grep.c
@@ -747,21 +747,41 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
 		if (opt->heading && !opt->file_break)
 			output_color(opt, "--", 2, opt->color_sep);
 		opt->output(opt, "\n", 1);
-		show_hunk = 0;
+		if (!opt->hunk_heading)
+			show_hunk = 0;
 	}
 	if (opt->heading && opt->last_shown == 0) {
 		output_color(opt, name, strlen(name), opt->color_filename);
 		opt->output(opt, "\n", 1);
 	}
 	if (show_hunk &&
-	    ((opt->last_shown == 0 && opt->show_hunk_mark) ||
+	    ((opt->last_shown == 0 && (opt->show_hunk_mark || opt->hunk_heading)) ||
 	     (opt->last_shown != 0 && lno > opt->last_shown + 1))) {
 		output_color(opt, "--", 2, opt->color_sep);
+		if (opt->hunk_heading &&
+		    ((!opt->heading && opt->pathname) || !opt->linenum)) {
+			opt->output(opt, " ", 1);
+			if (!opt->heading && opt->pathname) {
+				output_color(opt, name, strlen(name),
+					     opt->color_filename);
+				if (!opt->linenum)
+					output_sep(opt, ':');
+			}
+			if (!opt->linenum) {
+				char buf[32];
+				snprintf(buf, sizeof(buf), "%d", lno);
+				output_color(opt, buf, strlen(buf),
+					     opt->color_lineno);
+			}
+			opt->output(opt, " ", 1);
+			output_color(opt, "--", 2, opt->color_sep);
+		}
 		opt->output(opt, "\n", 1);
 	}
 	opt->last_shown = lno;
 
-	if (!opt->heading && opt->pathname) {
+	if (opt->pathname && !opt->heading &&
+	    !(opt->hunk_heading && show_hunk)) {
 		output_color(opt, name, strlen(name), opt->color_filename);
 		output_sep(opt, sign);
 	}
@@ -1001,7 +1021,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 		opt->output = std_output;
 
 	if (opt->pre_context || opt->post_context || opt->file_break ||
-	    opt->funcbody) {
+	    opt->funcbody || opt->hunk_heading) {
 		/* Show hunk marks, except for the first file. */
 		if (opt->last_shown)
 			opt->show_hunk_mark = 1;
diff --git a/grep.h b/grep.h
index 36e49d8..761db2a 100644
--- a/grep.h
+++ b/grep.h
@@ -117,6 +117,7 @@ struct grep_opt {
 	int show_hunk_mark;
 	int file_break;
 	int heading;
+	int hunk_heading;
 	void *priv;
 
 	void (*output)(struct grep_opt *opt, const void *data, size_t size);
diff --git a/t/t7812-grep-hunk-heading.sh b/t/t7812-grep-hunk-heading.sh
new file mode 100755
index 0000000..6db9ba6
--- /dev/null
+++ b/t/t7812-grep-hunk-heading.sh
@@ -0,0 +1,181 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Mark Lodato
+#
+
+test_description='git grep --hunk-heading'
+
+. ./test-lib.sh
+
+cat >one.c <<EOF
+#include <stdio.h>
+int main(int argc, const char **argv)
+{
+	printf("Hello world.\n");
+	return 0;
+}
+EOF
+
+cat >two.c <<EOF
+int hello_int(int x)
+{
+	printf("Hello, %d.\n", x);
+	return 0;
+}
+
+int hello_str(const char *s)
+{
+	printf("Hello, %s.\n", s);
+	return 0;
+}
+EOF
+
+test_expect_success 'setup' '
+	git add . &&
+	git commit -m initial
+'
+
+cat >expected <<EOF
+one.c:	printf("Hello world.\n");
+two.c:	printf("Hello, %d.\n", x);
+two.c:	printf("Hello, %s.\n", s);
+EOF
+
+test_expect_success 'grep --hunk-heading without context' '
+	git grep -e Hello --hunk-heading >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+-- one.c:4 --
+	printf("Hello world.\n");
+	return 0;
+-- two.c:3 --
+	printf("Hello, %d.\n", x);
+	return 0;
+-- two.c:9 --
+	printf("Hello, %s.\n", s);
+	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading -A1' '
+	git grep -e Hello -A1 --hunk-heading >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'grep --hunk-heading -B1' '
+	git grep -e return -B1 --hunk-heading >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+<RED>--<RESET> <BLUE>one.c<RESET><RED>:<RESET><GREEN>4<RESET> <RED>--<RESET>
+	printf("Hello world.\n");
+	return 0;
+<RED>--<RESET> <BLUE>two.c<RESET><RED>:<RESET><GREEN>3<RESET> <RED>--<RESET>
+	printf("Hello, %d.\n", x);
+	return 0;
+<RED>--<RESET> <BLUE>two.c<RESET><RED>:<RESET><GREEN>9<RESET> <RED>--<RESET>
+	printf("Hello, %s.\n", s);
+	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading --color' '
+	test_config color.grep.context		normal &&
+	test_config color.grep.filename		"blue" &&
+	test_config color.grep.function		normal &&
+	test_config color.grep.linenumber	"green" &&
+	test_config color.grep.match		normal &&
+	test_config color.grep.selected		normal &&
+	test_config color.grep.separator	"red" &&
+
+	git grep -e Hello -A1 --hunk-heading --color |
+	test_decode_color >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+-- one.c:4 --
+	printf("Hello world.\n");
+	return 0;
+
+-- two.c:3 --
+	printf("Hello, %d.\n", x);
+	return 0;
+-- two.c:9 --
+	printf("Hello, %s.\n", s);
+	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading --break' '
+	git grep -e Hello -A1 --hunk-heading --break >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+one.c
+-- 4 --
+	printf("Hello world.\n");
+	return 0;
+--
+two.c
+-- 3 --
+	printf("Hello, %d.\n", x);
+	return 0;
+-- 9 --
+	printf("Hello, %s.\n", s);
+	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading --heading' '
+	git grep -e Hello -A1 --hunk-heading --heading >actual &&
+	test_cmp expected actual
+'
+
+mv expected expected.old
+sed -e 's/^--$//' expected.old > expected
+rm expected.old
+
+test_expect_success 'grep --hunk-heading --heading --break' '
+	git grep -e Hello -A1 --hunk-heading --heading --break >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+-- one.c --
+4:	printf("Hello world.\n");
+5-	return 0;
+-- two.c --
+3:	printf("Hello, %d.\n", x);
+4-	return 0;
+-- two.c --
+9:	printf("Hello, %s.\n", s);
+10-	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading -n' '
+	git grep -e Hello -A1 --hunk-heading -n >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+one.c
+--
+4:	printf("Hello world.\n");
+5-	return 0;
+--
+two.c
+--
+3:	printf("Hello, %d.\n", x);
+4-	return 0;
+--
+9:	printf("Hello, %s.\n", s);
+10-	return 0;
+EOF
+
+test_expect_success 'grep --hunk-heading --heading -n' '
+	git grep -e Hello -A1 --hunk-heading --heading -n >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
1.7.9.2
Junio C Hamano· Mar 26, 2012, 05:14 UTC · re: Mark Lodato · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

Mark Lodato <lodatom@gmail.com> writes:
Show 13 quoted lines
> Originally, I had envisioned also moving the function name (`-p') to the hunk
> header, similar to the diff context line.  For example:
>
>     -- git.c:570 -- int main(int argc, char argv)
>                     printf("usage: %s\n\n", git_usage_string);
>                     list_common_cmds_help();
>                     printf("\n%s\n", git_more_info_string);
>
> After implementing this feature, I was not happy with the result and
> subsequently removed it.  To me, the output was too cluttered and the line
> number was ambigous.  For example, in the above, it is not obvious to me that
> line 570 is the "printf" line and not the "int main" line.  Still, if you
> would like to see the patch to implement this feature, please let me know.

The worst part of all of the above is that the output becomes utterly ambiguous and the reader cannot tell if "-- git.c..." came because the file had such a line that begin with two dashes in it and grep found it, or it is your output format embellishment. It is obvious that these are not meant to be machine parseable, but if the goal is to make the output more useful to the humans, then it may be a better approach to come up with a front end that reads our machine readable output and shows output with its own embellishments. You could even make it an interactive front end.

In other words, I am not yet convinced this belongs to "git grep" proper.
René Scharfe· Mar 26, 2012, 16:16 UTC · re: Junio C Hamano · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

Am 26.03.2012 07:14, schrieb Junio C Hamano:
Show 25 quoted lines
> Mark Lodato<lodatom@gmail.com>  writes:
>
>> Originally, I had envisioned also moving the function name (`-p') to the hunk
>> header, similar to the diff context line.  For example:
>>
>>      -- git.c:570 -- int main(int argc, char argv)
>>                      printf("usage: %s\n\n", git_usage_string);
>>                      list_common_cmds_help();
>>                      printf("\n%s\n", git_more_info_string);
>>
>> After implementing this feature, I was not happy with the result and
>> subsequently removed it.  To me, the output was too cluttered and the line
>> number was ambigous.  For example, in the above, it is not obvious to me that
>> line 570 is the "printf" line and not the "int main" line.  Still, if you
>> would like to see the patch to implement this feature, please let me know.
>
> The worst part of all of the above is that the output becomes utterly
> ambiguous and the reader cannot tell if "-- git.c..." came because the
> file had such a line that begin with two dashes in it and grep found it,
> or it is your output format embellishment. It is obvious that these are
> not meant to be machine parseable, but if the goal is to make the output
> more useful to the humans, then it may be a better approach to come up
> with a front end that reads our machine readable output and shows output
> with its own embellishments. You could even make it an interactive front
> end.

Human readers can differentiate between contents and heading by color; separators are cyan by default.

A separate frontend would probably have to implement match highlighting again. That's not too hard, but a bit sad.

> In other words, I am not yet convinced this belongs to "git grep" proper.

All in all, I'm not sure either. But I think the idea to deduplicate the meta-information and give found content more screen real estate is a good one in general.

René
Junio C Hamano· Mar 26, 2012, 18:05 UTC · re: René Scharfe · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:
> Human readers can differentiate between contents and heading by color;
> separators are cyan by default.
OK.
> A separate frontend would probably have to implement match
> highlighting again.  That's not too hard, but a bit sad.

Yeah, but at the same time, a separate front-end could do a lot more than just grep. Letting the user pick a function name from the current output and run grep again, letting the user highlight the line range and run blame (or "Linus's ultimate content tracking tool"), etc.

> ...  But I think the idea to deduplicate the meta-information and give
> found content more screen real estate is a good one in general.

Yeah, I found the "sound of one hand clapping" in your other message somewhat intriguing ;-).

Bert Wesarg· Mar 26, 2012, 18:48 UTC · re: Junio C Hamano · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

On Mon, Mar 26, 2012 at 07:14, Junio C Hamano <gitster@pobox.com> wrote:
Show 25 quoted lines
> Mark Lodato <lodatom@gmail.com> writes:
>
>> Originally, I had envisioned also moving the function name (`-p') to the hunk
>> header, similar to the diff context line.  For example:
>>
>>     -- git.c:570 -- int main(int argc, char argv)
>>                     printf("usage: %s\n\n", git_usage_string);
>>                     list_common_cmds_help();
>>                     printf("\n%s\n", git_more_info_string);
>>
>> After implementing this feature, I was not happy with the result and
>> subsequently removed it.  To me, the output was too cluttered and the line
>> number was ambigous.  For example, in the above, it is not obvious to me that
>> line 570 is the "printf" line and not the "int main" line.  Still, if you
>> would like to see the patch to implement this feature, please let me know.
>
> The worst part of all of the above is that the output becomes utterly
> ambiguous and the reader cannot tell if "-- git.c..." came because the
> file had such a line that begin with two dashes in it and grep found it,
> or it is your output format embellishment. It is obvious that these are
> not meant to be machine parseable, but if the goal is to make the output
> more useful to the humans, then it may be a better approach to come up
> with a front end that reads our machine readable output and shows output
> with its own embellishments. You could even make it an interactive front
> end.
FYI, I once posted a git-gui grep prototype:
<1289770869-11665-1-git-send-email-bert.wesarg@googlemail.com>

I have since added it to git-gui, see bw/master~2 'git-gui: add grep tab' in http://repo.or.cz/w/git-gui/bertw.git.

Bert
>
> In other words, I am not yet convinced this belongs to "git grep" proper.
>
>
René Scharfe· Mar 26, 2012, 16:16 UTC · re: Mark Lodato · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

Am 26.03.2012 04:41, schrieb Mark Lodato:
Show 50 quoted lines
> This patch series adds a new `grep --hunk-heading' option that moves the
> filename and line number to the hunk separator lines ("--") rather than at the
> beginning of each matching (or context) line.  In my opinion, this makes the
> output easier to read, especially when combined with `--heading'.
>
> I am not sure that "hunk-heading" is the best term, so I welcome ideas on
> better names.
>
> Here's an example:
>
>      # Current behavior:
>      $ git grep -p -C1 -n list_common -- git.c
>      git.c=531=int main(int argc, const char **argv)
>      --
>      git.c-570-              printf("usage: %s\n\n", git_usage_string);
>      git.c:571:              list_common_cmds_help();
>      git.c-572-              printf("\n%s\n", git_more_info_string);
>
>      # New option:
>      $ git grep -p -C1 --hunk-heading list_common -- git.c
>      -- git.c:531 --
>      int main(int argc, char argv)
>      -- git.c:570 --
>                      printf("usage: %s\n\n", git_usage_string);
>                      list_common_cmds_help();
>                      printf("\n%s\n", git_more_info_string);
>
>      # New option with --heading:
>      $ git grep -p -C1 --hunk-heading --heading list_common -- git.c
>      git.c
>      -- 531 --
>      int main(int argc, char argv)
>      -- 570 --
>                      printf("usage: %s\n\n", git_usage_string);
>                      list_common_cmds_help();
>                      printf("\n%s\n", git_more_info_string);
>
> Originally, I had envisioned also moving the function name (`-p') to the hunk
> header, similar to the diff context line.  For example:
>
>      -- git.c:570 -- int main(int argc, char argv)
>                      printf("usage: %s\n\n", git_usage_string);
>                      list_common_cmds_help();
>                      printf("\n%s\n", git_more_info_string);
>
> After implementing this feature, I was not happy with the result and
> subsequently removed it.  To me, the output was too cluttered and the line
> number was ambigous.  For example, in the above, it is not obvious to me that
> line 570 is the "printf" line and not the "int main" line.  Still, if you
> would like to see the patch to implement this feature, please let me know.
Interesting.

By the way, I keep this alias in my config (a single line), to mimic ack (http://betterthangrep.com/) -- another way to format results, with similar goals:

	ack = -c color.grep.filename='bold green' \
	-c color.grep.match='black yellow' grep --break --heading -n

Back to your patch: Why the second set of "--" after the line number? I can see it make sense if a section comment follows, but not without one.

Looking at the above, I thought: We have unified diffs between two files, we have combined diffs between more than two, what about showing grep results as one-sided unified diffs? ("What's the sound of one hand clapping?" :-)

	--- a/git.c
	@ -570,3 @ int main(int argc, const char **argv)
	-		printf("usage: %s\n\n", git_usage_string);
	:		list_common_cmds_help();
	-		printf("\n%s\n", git_more_info_string);
Pro: Generalization of an established format for showing interesting 
parts of a file.  Less duplication of meta-information.  Markers that 
tell us the kind of the shown lines are kept ("-" for context, ":" for 
matches).  Machine parsable.
Con: Why the "a/" prefix?  One-sided diffs, srsly?
René
Junio C Hamano· Mar 26, 2012, 18:01 UTC · re: René Scharfe · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:
Show 17 quoted lines
> Looking at the above, I thought: We have unified diffs between two
> files, we have combined diffs between more than two, what about
> showing grep results as one-sided unified diffs?  ("What's the sound
> of one hand clapping?" :-)
>
> 	--- a/git.c
> 	@ -570,3 @ int main(int argc, const char **argv)
> 	-		printf("usage: %s\n\n", git_usage_string);
> 	:		list_common_cmds_help();
> 	-		printf("\n%s\n", git_more_info_string);
>
> Pro: Generalization of an established format for showing interesting
> parts of a file.  Less duplication of meta-information.  Markers that
> tell us the kind of the shown lines are kept ("-" for context, ":" for
> matches).  Machine parsable.
>
> Con: Why the "a/" prefix?  One-sided diffs, srsly?

Cute, and I tend to agree that this is probably easier to read if you are used to reading unified diffs.

Wouldn't it make more sense to replace your '-/:' with ' /=', so that at least ' ' SP retains the meaning of "this is shown merely to give you context, it is not a proper part of what you are looking for"?

The reasoning behind '=' is that it is not either -/+ as we are not really comparing anything with anything. It may also make sense to replace the per-file header line with "=== git.c" to be consistent. I haven't formed an opinion on the prefix yet; there might be a good reason to keep the depth of the path each file appear in this "grep --unidiff-like" output and "diff --patch" output, in which case "=== a/git.c" or "=== ./git.c" might be give us more uniformity.

René Scharfe· Mar 26, 2012, 21:12 UTC · re: Junio C Hamano · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

Am 26.03.2012 20:01, schrieb Junio C Hamano:
Show 26 quoted lines
> René Scharfe<rene.scharfe@lsrfire.ath.cx>  writes:
>
>> Looking at the above, I thought: We have unified diffs between two
>> files, we have combined diffs between more than two, what about
>> showing grep results as one-sided unified diffs?  ("What's the sound
>> of one hand clapping?" :-)
>>
>> 	--- a/git.c
>> 	@ -570,3 @ int main(int argc, const char **argv)
>> 	-		printf("usage: %s\n\n", git_usage_string);
>> 	:		list_common_cmds_help();
>> 	-		printf("\n%s\n", git_more_info_string);
>>
>> Pro: Generalization of an established format for showing interesting
>> parts of a file.  Less duplication of meta-information.  Markers that
>> tell us the kind of the shown lines are kept ("-" for context, ":" for
>> matches).  Machine parsable.
>>
>> Con: Why the "a/" prefix?  One-sided diffs, srsly?
>
> Cute, and I tend to agree that this is probably easier to read if you are
> used to reading unified diffs.
>
> Wouldn't it make more sense to replace your '-/:' with ' /=', so that at
> least ' ' SP retains the meaning of "this is shown merely to give you
> context, it is not a proper part of what you are looking for"?

Ah, good idea, the space makes for a less cluttered output. For completeness sake, I have to mention that the current grep output uses '-/:/=', however, for context/match/function line ("especially interesting context"). Even function lines that happen to fall within the --context are marked with an equal sign. That's not the case for unified diffs; they simply get shown as normal context.

> The reasoning behind '=' is that it is not either -/+ as we are not really
> comparing anything with anything.

Mapping the '-/:/=' of grep to ' /:/=' or ' /:/ ' might be easier to understand. However, seeing a line starting with a colon or an equal sign feels both strange, because they are normally used as binary operators. Normal grep output shows a filename or a line number before the separator, so it doesn't invoke that strange feeling.

Perhaps mapping to ' /!/ ' is better instead, similar to context diffs?
> It may also make sense to replace the
> per-file header line with "=== git.c" to be consistent.
A context diffs would have '*** git.c', but they are ugly IMHO, overall.

What we also could do: Produce a valid unified diff that would remove the matching lines if we were to apply it (or the --reverse, i.e. + instead of -). Then we wouldn't need to invent a special format, but the output would be a bit more verbose due to the added +++ lines.

I guess it's time to implement these options in order to try them out against real code. Won't have time to do so before the second half of the week, however.

René
Junio C Hamano· Mar 26, 2012, 21:19 UTC · re: René Scharfe · lore

Re: [PATCH 0/4] grep: add more information to hunk separators

René Scharfe <rene.scharfe@lsrfire.ath.cx> writes:
> What we also could do: Produce a valid unified diff that would remove
> the matching lines if we were to apply it (or the --reverse, i.e. +
> instead of -).  Then we wouldn't need to invent a special format, but
> the output would be a bit more verbose due to the added +++ lines.
Hrm, certainly that is an option that saves a lot of thinking.

As people tend to learn to focus more on '+' lines when reading patches in the unified context format, the reverse option would produce output that is easier to read, I would guess.

> I guess it's time to implement these options in order to try them out
> against real code.  Won't have time to do so before the second half of
> the week, however.

That's OK---we are in no hurry. Have you heard about pre-release feature freeze already ;-)?

Mark Lodato· Mar 27, 2012, 05:31 UTC · re: Mark Lodato · lore

[PATCH 5/4] move sane_truncate_line to utf8_truncate_line

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---

As promised, here are the additional patches to move the function name to the same line as the hunk header.

 diff.c |   14 ++------------
 utf8.c |   13 +++++++++++++
 utf8.h |    2 ++
 3 files changed, 17 insertions(+), 12 deletions(-)
Show changes to 3 files +17 −12

diff.c, utf8.c, utf8.h

diff --git a/diff.c b/diff.c
index 377ec1e..74c77bc 100644
--- a/diff.c
+++ b/diff.c
@@ -1015,20 +1015,10 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix)
 
 static unsigned long sane_truncate_line(struct emit_callback *ecb, char *line, unsigned long len)
 {
-	const char *cp;
-	unsigned long allot;
-	size_t l = len;
-
 	if (ecb->truncate)
 		return ecb->truncate(line, len);
-	cp = line;
-	allot = l;
-	while (0 < l) {
-		(void) utf8_width(&cp, &l);
-		if (!cp)
-			break; /* truncated in the middle? */
-	}
-	return allot - l;
+	else
+		return utf8_truncate_line(line, len);
 }
 
 static void find_lno(const char *line, struct emit_callback *ecbdata)
diff --git a/utf8.c b/utf8.c
index 8acbc66..d70ee9b 100644
--- a/utf8.c
+++ b/utf8.c
@@ -482,3 +482,16 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
 	return out;
 }
 #endif
+
+unsigned long utf8_truncate_line(const char *line, unsigned long len)
+{
+	const char *cp = line;
+	unsigned long allot = len;
+	size_t l = len;
+	while (0 < l) {
+		(void) utf8_width(&cp, &l);
+		if (!cp)
+			break; /* truncated in the middle? */
+	}
+	return allot - l;
+}
diff --git a/utf8.h b/utf8.h
index 81f2c82..929e2df 100644
--- a/utf8.h
+++ b/utf8.h
@@ -19,4 +19,6 @@ char *reencode_string(const char *in, const char *out_encoding, const char *in_e
 #define reencode_string(a,b,c) NULL
 #endif
 
+unsigned long utf8_truncate_line(const char *line, unsigned long len);
+
 #endif
-- 
1.7.9.4
Mark Lodato· Mar 27, 2012, 05:31 UTC · re: Mark Lodato · lore

[PATCH 6/4] add grep.hunkHeadingFunction option

If set to true, the function line is printed on the same line as the first hunk header (but only if a hunk header is printed).

Signed-off-by: Mark Lodato <lodatom@gmail.com>
---

I was not sure if it would be better to have this as a configuration setting, a command-line option, or both. It probably doesn't matter for this round, since the main purpose of this post is to get the idea out there so people can try it out this feature see if they like it at all.

There might have been a better way to implement this. Currently, we have the entire file in memory, so I could have just stored a pointer to the function name line, rather than copying it. Perhaps this would have been better?

Also, I chose to only print the function name once, rather than duplicating it on each header, so as to reduce visual clutter. I can see an argument both ways, so if you would like to have the function name printed on every header, just remove the "opt->func_line[0] = '\0'" from the beginning of show_funcname_line().

 Documentation/config.txt   |    5 +++++
 Documentation/git-grep.txt |    5 +++++
 builtin/grep.c             |    5 +++++
 grep.c                     |   24 +++++++++++++++++++++++-
 grep.h                     |    3 +++
 5 files changed, 41 insertions(+), 1 deletion(-)
Show changes to 5 files +41 −1

Documentation/config.txt, Documentation/git-grep.txt, builtin/grep.c, grep.c, grep.h

diff --git a/Documentation/config.txt b/Documentation/config.txt
index ade9503..1e3b5ec 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1143,6 +1143,11 @@ grep.extendedRegexp::
 grep.hunkHeading::
 	If set to true, enable '--hunk-heading' option by default.
 
+grep.hunkHeadingFunction::
+	If set to true, print the function name in the hunk heading rather
+	than on its own line.  This only occurs when hunk headings would have
+	been shown and '--show-function' is used.
+
 gpg.program::
 	Use this custom program instead of "gpg" found on $PATH when
 	making or verifying a PGP signature. The program must support the
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index 26c085b..a32ac5e 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -46,6 +46,11 @@ grep.extendedRegexp::
 grep.hunkHeading::
 	If set to true, enable '--hunk-heading' option by default.
 
+grep.hunkHeadingFunction::
+	If set to true, print the function name in the hunk heading rather
+	than on its own line.  This only occurs when hunk headings would have
+	been shown and '--show-function' is used.
+
 
 OPTIONS
 -------
diff --git a/builtin/grep.c b/builtin/grep.c
index cdafc5a..d4c9f92 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -281,6 +281,11 @@ static int grep_config(const char *var, const char *value, void *cb)
 		return 0;
 	}
 
+	if (!strcmp(var, "grep.hunkheadingfunction")) {
+		opt->hunk_heading_function = git_config_bool(var, value);
+		return 0;
+	}
+
 	if (!strcmp(var, "grep.linenumber")) {
 		opt->linenum = git_config_bool(var, value);
 		return 0;
diff --git a/grep.c b/grep.c
index f0e00f7..49e66e3 100644
--- a/grep.c
+++ b/grep.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "grep.h"
 #include "userdiff.h"
+#include "utf8.h"
 #include "xdiff-interface.h"
 
 void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
@@ -776,6 +777,12 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
 			opt->output(opt, " ", 1);
 			output_color(opt, "--", 2, opt->color_sep);
 		}
+		if (opt->hunk_heading && opt->func_line[0] != '\0') {
+			opt->output(opt, " ", 1);
+			output_color(opt, opt->func_line,
+				     strlen(opt->func_line),
+				     opt->color_function);
+		}
 		opt->output(opt, "\n", 1);
 	}
 	opt->last_shown = lno;
@@ -882,6 +889,7 @@ static int match_funcname(struct grep_opt *opt, struct grep_source *gs, char *bo
 static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
 			       char *bol, unsigned lno)
 {
+	opt->func_line[0] = '\0';
 	while (bol > gs->buf) {
 		char *eol = --bol;
 
@@ -893,7 +901,18 @@ static void show_funcname_line(struct grep_opt *opt, struct grep_source *gs,
 			break;
 
 		if (match_funcname(opt, gs, bol, eol)) {
-			show_line(opt, bol, eol, gs->name, lno, '=');
+			if (opt->hunk_heading_function && opt->hunk_heading &&
+			    opt->funcname &&
+			    (opt->pre_context || opt->post_context ||
+			     opt->funcbody)) {
+				unsigned long len = eol - bol;
+				if (len > GREP_MAX_FUNCLINE)
+					len = GREP_MAX_FUNCLINE;
+				len = utf8_truncate_line(bol, len);
+				memcpy(opt->func_line, bol, len);
+				opt->func_line[len] = '\0';
+			} else
+				show_line(opt, bol, eol, gs->name, lno, '=');
 			break;
 		}
 	}
@@ -930,6 +949,8 @@ static void show_pre_context(struct grep_opt *opt, struct grep_source *gs,
 	/* We need to look even further back to find a function signature. */
 	if (opt->funcname && funcname_needed)
 		show_funcname_line(opt, gs, bol, cur);
+	else
+		opt->func_line[0] = '\0';
 
 	/* Back forward. */
 	while (cur < lno) {
@@ -1034,6 +1055,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle
 			opt->show_hunk_mark = 1;
 	}
 	opt->last_shown = 0;
+	opt->func_line[0] = '\0';
 
 	switch (opt->binary) {
 	case GREP_BINARY_DEFAULT:
diff --git a/grep.h b/grep.h
index 761db2a..8112e07 100644
--- a/grep.h
+++ b/grep.h
@@ -118,6 +118,9 @@ struct grep_opt {
 	int file_break;
 	int heading;
 	int hunk_heading;
+	int hunk_heading_function;
+#define GREP_MAX_FUNCLINE	80
+	char func_line[GREP_MAX_FUNCLINE+1];
 	void *priv;
 
 	void (*output)(struct grep_opt *opt, const void *data, size_t size);
-- 
1.7.9.4

← back to recent threads