git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 4/4] ref-filter: avoid strrchr() in rstrip_ref_components()

From
Jeff King <peff@peff.net>
Date
Feb 15, 2026, 09:07 UTC
Message-ID
<20260215090744.GD695631@coredump.intra.peff.net>
In-Reply-To
<20260215085755.GA86262@coredump.intra.peff.net>

To strip path components from our refname string, we repeatedly call strrchr() to find the trailing slash, shortening the string each time by assigning NUL over it. This has two downsides:

  1. Calling strrchr() in a loop is quadratic, since each call has to
     call strlen() under the hood to find the end of the string (even
     though we know exactly where it is from the last loop iteration).
  2. We need a temporary buffer, since we're munging the string with NUL
     as we shorten it (which we must do, because strrchr() has no other
     way of knowing what we consider the end of the string).

Using memrchr() would let us fix both of these, but it isn't portable. So instead, let's just open-code the string traversal from back to front as we loop.

I doubt that the quadratic nature is a serious concern. You can see it in practice with something like:

  git init
  git commit --allow-empty -m foo
  echo "$(git rev-parse HEAD) refs/heads$(perl -e 'print "/a" x 500_000')" >.git/packed-refs
  time git for-each-ref --format='%(refname:rstrip=-1)'

That takes ~5.5s to run on my machine before this patch, and ~11ms after. But I don't think there's a reasonable way for somebody to infect you with such a garbage ref, as the wire protocol is limited to 64k pkt-lines. The difference is measurable for me for a 32k-component ref (about 19ms vs 7ms), so perhaps you could create some chaos by pushing a lot of them. But we also run into filesystem limits (if the loose backend is in use), and in practice it seems like there are probably simpler and more effective ways to waste CPU.

Likewise the extra allocation probably isn't really measurable. In fact, since our goal is to return an allocated string, we end up having to make the same allocation anyway (though it is sized to the result, rather than the input). My main goal was simplicity in avoiding the need to handle cleaning it up in the early return path.

Signed-off-by: Jeff King <peff@peff.net>
---
 ref-filter.c | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/ref-filter.c b/ref-filter.c
index 1008b2fd5a..ac32b0e6bb 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -2213,17 +2213,15 @@ static const char *lstrip_ref_components(const char *refname, int len)
 static const char *rstrip_ref_components(const char *refname, int len)
 {
 	int remaining = normalize_component_count(refname, len);
-	char *start = xstrdup(refname);
+	const char *end = refname + strlen(refname);
 
-	while (remaining-- > 0) {
-		char *p = strrchr(start, '/');
-		if (!p) {
-			free(start);
+	while (remaining > 0) {
+		if (end == refname)
 			return xstrdup("");
-		} else
-			p[0] = '\0';
+		if (*--end == '/')
+			remaining--;
 	}
-	return start;
+	return xmemdupz(refname, end - refname);
 }
 
 static const char *show_ref(struct refname_atom *atom, const char *refname)
-- 
2.53.0.438.gad17e1cd28
Previous: Jeff KingNext: Patrick Steinhardt
Message 11 of 14 in “ref-filter: don't declare a strdup'd variable const before writing to it”
  1. ref-filter: don't declare a strdup'd variable const before writing to itCollin Funk, Feb 14, 2026
  2. 0/4 cleaning up ref-filter lstrip/rstrip codeJeff King, Feb 15, 2026
  3. 1/4 ref-filter: factor out refname component countingJeff King, Feb 15, 2026
  4. Junio C HamanoFeb 17, 2026
  5. Jeff KingFeb 19, 2026
  6. Junio C HamanoFeb 19, 2026
  7. ref-filter: clarify lstrip/rstrip component countingJeff King, Feb 20, 2026
  8. Karthik NayakFeb 22, 2026
  9. 2/4 ref-filter: simplify lstrip_ref_components() memory handlingJeff King, Feb 15, 2026
  10. 3/4 ref-filter: simplify rstrip_ref_components() memory handlingJeff King, Feb 15, 2026
  11. 4/4 ref-filter: avoid strrchr() in rstrip_ref_components()Jeff King, Feb 15, 2026
  12. Patrick SteinhardtFeb 16, 2026
  13. Jeff KingFeb 15, 2026
  14. Collin FunkFeb 15, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.