Subject: [PATCH] path: refactor normalize_path_copy_len()

From: Pushkar Singh <[email-masked]>
Date: Thu, 29 Jan 2026 14:54:35 +0000

Refactor normalize_path_copy_len() by extracting helpers for skipping
slashes, handling dot components, and stripping the previous path
component, making the control flow easier to follow.

This is a mechanical refactor only; there are no functional changes.
Behavior is unchanged, as verified by t0060-path-utils.sh.

Signed-off-by: Pushkar Singh <[email-masked]>
---
 path.c | 105 ++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 67 insertions(+), 38 deletions(-)

diff --git a/path.c b/path.c
index d726537622..00845cc03f 100644
--- a/path.c
+++ b/path.c
@@ -1112,6 +1112,63 @@ const char *remove_leading_path(const char *in, const char *prefix)
  * end with a '/', then the callers need to be fixed up accordingly.
  *
  */
+
+static const char *skip_slashes(const char *p)
+{
+	while (is_dir_sep(*p))
+		p++;
+	return p;
+}
+
+static int handle_dot_component(const char **src)
+{
+	const char *s = *src;
+
+	if (*s != '.')
+		return 0;
+
+	if (!s[1]) {
+		*src = s + 1;
+		return 1;
+	}
+
+	if (is_dir_sep(s[1])) {
+		*src = skip_slashes(s + 2);
+		return 1;
+	}
+
+	if (s[1] == '.') {
+		if (!s[2]) {
+			*src = s + 2;
+			return 2;
+		}
+		if (is_dir_sep(s[2])) {
+			*src = skip_slashes(s + 3);
+			return 2;
+		}
+	}
+
+	return 0;
+}
+
+static int strip_last_component(char **dst, char *dst0, int *prefix_len)
+{
+	char *d = *dst;
+
+	d--;
+	if (d <= dst0)
+		return -1;
+
+	while (dst0 < d && d[-1] != '/')
+		d--;
+
+	if (prefix_len && *prefix_len > d - dst0)
+		*prefix_len = d - dst0;
+
+	*dst = d;
+	return 0;
+}
+
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
@@ -1129,8 +1186,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 	}
 	dst0 = dst;
 
-	while (is_dir_sep(*src))
-		src++;
+	src = skip_slashes(src);
 
 	for (;;) {
 		char c = *src;
@@ -1143,29 +1199,14 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 		 * (3) ".." and ends  -- strip one and terminate.
 		 * (4) "../"          -- strip one, eat slash and continue.
 		 */
-		if (c == '.') {
-			if (!src[1]) {
-				/* (1) */
-				src++;
-			} else if (is_dir_sep(src[1])) {
-				/* (2) */
-				src += 2;
-				while (is_dir_sep(*src))
-					src++;
-				continue;
-			} else if (src[1] == '.') {
-				if (!src[2]) {
-					/* (3) */
-					src += 2;
-					goto up_one;
-				} else if (is_dir_sep(src[2])) {
-					/* (4) */
-					src += 3;
-					while (is_dir_sep(*src))
-						src++;
-					goto up_one;
-				}
-			}
+		int dot = handle_dot_component(&src);
+
+		if (dot == 1)
+			continue;
+		if (dot == 2) {
+			if (strip_last_component(&dst, dst0, prefix_len))
+				return -1;
+			continue;
 		}
 
 		/* copy up to the next '/', and eat all '/' */
@@ -1180,20 +1221,8 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 			break;
 		continue;
 
-	up_one:
-		/*
-		 * dst0..dst is prefix portion, and dst[-1] is '/';
-		 * go up one level.
-		 */
-		dst--;	/* go to trailing '/' */
-		if (dst <= dst0)
-			return -1;
-		/* Windows: dst[-1] cannot be backslash anymore */
-		while (dst0 < dst && dst[-1] != '/')
-			dst--;
-		if (prefix_len && *prefix_len > dst - dst0)
-			*prefix_len = dst - dst0;
 	}
+
 	*dst = '\0';
 	return 0;
 }
-- 
2.43.0