Subject: Re: git grep bug with --column and --only-matching

From: René Scharfe <[email-masked]>
Date: Tue, 21 Apr 2026 22:33:04 +0200

On 4/21/26 7:03 AM, Brandon Chinn wrote:
> I'm encountering a bug when using `git grep` with both `--column` and
> `--only-matching`, is this a known limitation?
> 
> Repro:
> 
> ```
> $ echo 'x   x   x' > repro.txt
> 
> $ grep -bo x repro.txt
> 0:x
> 4:x
> 8:x
> 
> $ git grep --no-index -o -n --column x repro.txt
> repro.txt:1:  1:x
> repro.txt:1:  2:x
> repro.txt:1:  6:x

The first column value matches (1-based for git grep, 0-based for
grep(1)), the remaining ones are different.  grep(1) shows the offset
like for the first match, but git grep adds the relative position of the
end of the previous match.

I don't know how to use the resulting numbers and I agree that it seems
like a bug -- showing the column of each match within its line makes
more sense for an option named --column.

However, the original submission of this feature in
https://lore.kernel.org/git/[email-masked]/
gave similar examples in its commit message and its tests, called
them "as one would expect" and was not challenged on that, so I may be
missing something.

Here's its first example with underlined matches, a ruler and the
intended output (without unnecessary headers):

  (`man gitcvs-migration` or `git help cvs-migration` if git is
        ^^^                   ^^^                        ^^^
           1111111111222222222233333333334444444444555555555566  
  123456789 123456789 123456789 123456789 123456789 123456789 1
           
  7:git
  16:git
  38:git

And here's the last line of the test file from t7810 with underlined
matches (the other four lines are very similar), a ruler and the
expected output (unncessary headers removed):

  foo_mmap bar mmap baz
      ^^^^     ^^^^
           111111111122
  123456789 123456789 1

  5:mmap
  13:mmap

If we wanted to show the column of matches 2 and beyond then we could do
something like this:

diff --git a/grep.c b/grep.c
index c7e1dc1e0e..a54e5d86a9 100644
--- a/grep.c
+++ b/grep.c
@@ -1267,6 +1267,7 @@ static void show_line(struct grep_opt *opt,
 		regmatch_t match;
 		enum grep_context ctx = GREP_CONTEXT_BODY;
 		int eflags = 0;
+		const char *start = bol;
 
 		if (want_color(opt->color)) {
 			if (sign == ':')
@@ -1285,6 +1286,7 @@ static void show_line(struct grep_opt *opt,
 			if (match.rm_so == match.rm_eo)
 				break;
 
+			cno = bol - start + match.rm_so + 1;
 			if (opt->only_matching)
 				show_line_header(opt, name, lno, cno, sign);
 			else
@@ -1294,7 +1296,6 @@ static void show_line(struct grep_opt *opt,
 			if (opt->only_matching)
 				opt->output(opt, "\n", 1);
 			bol += match.rm_eo;
-			cno += match.rm_eo;
 			rest -= match.rm_eo;
 			eflags = REG_NOTBOL;
 		}
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index 64ac4f04ee..bd439563d6 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -322,11 +322,11 @@ do
 		${HC}file:1:5:mmap
 		${HC}file:2:5:mmap
 		${HC}file:3:5:mmap
-		${HC}file:3:13:mmap
+		${HC}file:3:14:mmap
 		${HC}file:4:5:mmap
-		${HC}file:4:13:mmap
+		${HC}file:4:14:mmap
 		${HC}file:5:5:mmap
-		${HC}file:5:13:mmap
+		${HC}file:5:14:mmap
 		EOF
 		git grep --column -n -o -e mmap $H >actual &&
 		test_cmp expected actual