threads / patch / 28612

v3, 5 partscleanup: use internal memory allocation wrapper functions everywhere

Subject: [PATCH v3 2/5] cleanup: use internal memory allocation wrapper functions everywhere

## tl;dr

13 messages between Oct 6, 2011 and Oct 12, 2011. Diffs are folded; open one to read it.

replies: 12people: 3as markdown or json

Brandon Casey· Oct 6, 2011, 18:22 UTC · lore

[PATCH v3 1/5] attr.c: avoid inappropriate access to strbuf "buf" member

From: Brandon Casey <drafnel@gmail.com>

This code sequence performs a strcpy into the buf member of a strbuf struct. The strcpy may move the position of the terminating nul of the string and effectively change the length of string so that it does not match the len member of the strbuf struct.

Currently, this sequence works since the strbuf was given a hint when it was initialized to allocate enough space to accomodate the string that will be strcpy'ed, but this is an implementation detail of strbufs, not a guarantee.

So, lets rework this sequence so that the strbuf is only manipulated by strbuf functions, and direct modification of its "buf" member is not necessary.

Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
 attr.c |   24 +++++++++++-------------
 1 files changed, 11 insertions(+), 13 deletions(-)
Show changes to attr.c +11 −13
diff --git a/attr.c b/attr.c
index 33cb4e4..fe38fcc 100644
--- a/attr.c
+++ b/attr.c
@@ -552,7 +552,6 @@ static void prepare_attr_stack(const char *path)
 {
 	struct attr_stack *elem, *info;
 	int dirlen, len;
-	struct strbuf pathbuf;
 	const char *cp;
 
 	cp = strrchr(path, '/');
@@ -561,8 +560,6 @@ static void prepare_attr_stack(const char *path)
 	else
 		dirlen = cp - path;
 
-	strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
-
 	/*
 	 * At the bottom of the attribute stack is the built-in
 	 * set of attribute definitions, followed by the contents
@@ -607,27 +604,28 @@ static void prepare_attr_stack(const char *path)
 	 * Read from parent directories and push them down
 	 */
 	if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-		while (1) {
-			char *cp;
+		struct strbuf pathbuf = STRBUF_INIT;
 
+		while (1) {
 			len = strlen(attr_stack->origin);
 			if (dirlen <= len)
 				break;
-			strbuf_reset(&pathbuf);
-			strbuf_add(&pathbuf, path, dirlen);
+			cp = memchr(path + len + 1, '/', dirlen - len - 1);
+			if (!cp)
+				cp = path + dirlen;
+			strbuf_add(&pathbuf, path, cp - path);
 			strbuf_addch(&pathbuf, '/');
-			cp = strchr(pathbuf.buf + len + 1, '/');
-			strcpy(cp + 1, GITATTRIBUTES_FILE);
+			strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
 			elem = read_attr(pathbuf.buf, 0);
-			*cp = '\0';
-			elem->origin = strdup(pathbuf.buf);
+			strbuf_setlen(&pathbuf, cp - path);
+			elem->origin = strbuf_detach(&pathbuf, NULL);
 			elem->prev = attr_stack;
 			attr_stack = elem;
 			debug_push(elem);
 		}
-	}
 
-	strbuf_release(&pathbuf);
+		strbuf_release(&pathbuf);
+	}
 
 	/*
 	 * Finally push the "info" one at the top of the stack.
-- 
1.7.7
Brandon Casey· Oct 6, 2011, 18:22 UTC · lore
From: Brandon Casey <drafnel@gmail.com>

The "x"-prefixed versions of strdup, malloc, etc. will check whether the allocation was successful and terminate the process otherwise.

A few uses of malloc were left alone since they already implemented a graceful path of failure or were in a quasi external library like xdiff.

Additionally, the call to malloc in compat/win32/syslog.c was not modified since the syslog() implemented there is a die handler and a call to the x-wrappers within a die handler could result in recursion should memory allocation fail. This will have to be addressed separately.

Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
 attr.c             |    2 +-
 builtin/mv.c       |    2 +-
 compat/mingw.c     |    2 +-
 compat/qsort.c     |    2 +-
 remote.c           |    2 +-
 show-index.c       |    2 +-
 transport-helper.c |    4 ++--
 7 files changed, 8 insertions(+), 8 deletions(-)
Show changes to 7 files +8 −8

attr.c, builtin/mv.c, compat/mingw.c, compat/qsort.c, remote.c, show-index.c, transport-helper.c

diff --git a/attr.c b/attr.c
index fe38fcc..0793859 100644
--- a/attr.c
+++ b/attr.c
@@ -533,7 +533,7 @@ static void bootstrap_attr_stack(void)
 
 		if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
 			elem = read_attr(GITATTRIBUTES_FILE, 1);
-			elem->origin = strdup("");
+			elem->origin = xstrdup("");
 			elem->prev = attr_stack;
 			attr_stack = elem;
 			debug_push(elem);
diff --git a/builtin/mv.c b/builtin/mv.c
index 40f33ca..e9d191f 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -29,7 +29,7 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
 			to_copy--;
 		if (to_copy != length || base_name) {
 			char *it = xmemdupz(result[i], to_copy);
-			result[i] = base_name ? strdup(basename(it)) : it;
+			result[i] = base_name ? xstrdup(basename(it)) : it;
 		}
 	}
 	return get_pathspec(prefix, result);
diff --git a/compat/mingw.c b/compat/mingw.c
index 6ef0cc4..8947418 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1183,7 +1183,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
 	}
 	ai->ai_addrlen = sizeof(struct sockaddr_in);
 	if (hints && (hints->ai_flags & AI_CANONNAME))
-		ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+		ai->ai_canonname = h ? xstrdup(h->h_name) : NULL;
 	else
 		ai->ai_canonname = NULL;
 
diff --git a/compat/qsort.c b/compat/qsort.c
index d93dce2..9574d53 100644
--- a/compat/qsort.c
+++ b/compat/qsort.c
@@ -55,7 +55,7 @@ void git_qsort(void *b, size_t n, size_t s,
 		msort_with_tmp(b, n, s, cmp, buf);
 	} else {
 		/* It's somewhat large, so malloc it.  */
-		char *tmp = malloc(size);
+		char *tmp = xmalloc(size);
 		msort_with_tmp(b, n, s, cmp, tmp);
 		free(tmp);
 	}
diff --git a/remote.c b/remote.c
index b8ecfa5..7840d2f 100644
--- a/remote.c
+++ b/remote.c
@@ -840,7 +840,7 @@ char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
 						    refspec->dst, &ret))
 				return ret;
 		} else if (!strcmp(refspec->src, name))
-			return strdup(refspec->dst);
+			return xstrdup(refspec->dst);
 	}
 	return NULL;
 }
diff --git a/show-index.c b/show-index.c
index 4c0ac13..63f9da5 100644
--- a/show-index.c
+++ b/show-index.c
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
 			unsigned char sha1[20];
 			uint32_t crc;
 			uint32_t off;
-		} *entries = malloc(nr * sizeof(entries[0]));
+		} *entries = xmalloc(nr * sizeof(entries[0]));
 		for (i = 0; i < nr; i++)
 			if (fread(entries[i].sha1, 20, 1, stdin) != 1)
 				die("unable to read sha1 %u/%u", i, nr);
diff --git a/transport-helper.c b/transport-helper.c
index 4eab844..0713126 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -183,7 +183,7 @@ static struct child_process *get_helper(struct transport *transport)
 			ALLOC_GROW(refspecs,
 				   refspec_nr + 1,
 				   refspec_alloc);
-			refspecs[refspec_nr++] = strdup(capname + strlen("refspec "));
+			refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
 		} else if (!strcmp(capname, "connect")) {
 			data->connect = 1;
 		} else if (!prefixcmp(capname, "export-marks ")) {
@@ -445,7 +445,7 @@ static int fetch_with_import(struct transport *transport,
 		if (data->refspecs)
 			private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
 		else
-			private = strdup(posn->name);
+			private = xstrdup(posn->name);
 		read_ref(private, posn->old_sha1);
 		free(private);
 	}
-- 
1.7.7
Brandon Casey· Oct 6, 2011, 18:22 UTC · lore

[PATCH v3 3/5] builtin/mv.c: plug miniscule memory leak

From: Brandon Casey <drafnel@gmail.com>

The "it" string would not be free'ed if base_name was non-NULL. Let's free it.

Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
 builtin/mv.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)
Show changes to builtin/mv.c +5 −1
diff --git a/builtin/mv.c b/builtin/mv.c
index e9d191f..5efe6c5 100644
--- a/builtin/mv.c
+++ b/builtin/mv.c
@@ -29,7 +29,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
 			to_copy--;
 		if (to_copy != length || base_name) {
 			char *it = xmemdupz(result[i], to_copy);
-			result[i] = base_name ? xstrdup(basename(it)) : it;
+			if (base_name) {
+				result[i] = xstrdup(basename(it));
+				free(it);
+			} else
+				result[i] = it;
 		}
 	}
 	return get_pathspec(prefix, result);
-- 
1.7.7
Brandon Casey· Oct 6, 2011, 18:22 UTC · lore

[PATCH v3 4/5] attr: read core.attributesfile from git_default_core_config

From: Junio C Hamano <gitster@pobox.com>

This code calls git_config from a helper function to parse the config entry it is interested in. Calling git_config in this way may cause a problem if the helper function can be called after a previous call to git_config by another function since the second call to git_config may reset some variable to the value in the config file which was previously overridden.

The above is not a problem in this case since the function passed to git_config only parses one config entry and the variable it sets is not assigned outside of the parsing function. But a programmer who desires all of the standard config options to be parsed may be tempted to modify git_attr_config() so that it falls back to git_default_config() and then it _would_ be vulnerable to the above described behavior.

So, move the call to git_config up into the top-level cmd_* function and move the responsibility for parsing core.attributesfile into the main config file parser.

Which is only the logical thing to do ;-)
Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
 attr.c               |   15 ++-------------
 builtin/check-attr.c |    2 ++
 cache.h              |    1 +
 config.c             |    3 +++
 environment.c        |    1 +
 5 files changed, 9 insertions(+), 13 deletions(-)
Show changes to 5 files +9 −13

attr.c, builtin/check-attr.c, cache.h, config.c, environment.c

diff --git a/attr.c b/attr.c
index 0793859..124337d 100644
--- a/attr.c
+++ b/attr.c
@@ -20,8 +20,6 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 #define ATTR__UNSET NULL
 #define ATTR__UNKNOWN git_attr__unknown
 
-static const char *attributes_file;
-
 /* This is a randomly chosen prime. */
 #define HASHSIZE 257
 
@@ -494,14 +492,6 @@ static int git_attr_system(void)
 	return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
 }
 
-static int git_attr_config(const char *var, const char *value, void *dummy)
-{
-	if (!strcmp(var, "core.attributesfile"))
-		return git_config_pathname(&attributes_file, var, value);
-
-	return 0;
-}
-
 static void bootstrap_attr_stack(void)
 {
 	if (!attr_stack) {
@@ -521,9 +511,8 @@ static void bootstrap_attr_stack(void)
 			}
 		}
 
-		git_config(git_attr_config, NULL);
-		if (attributes_file) {
-			elem = read_attr_from_file(attributes_file, 1);
+		if (git_attributes_file) {
+			elem = read_attr_from_file(git_attributes_file, 1);
 			if (elem) {
 				elem->origin = NULL;
 				elem->prev = attr_stack;
diff --git a/builtin/check-attr.c b/builtin/check-attr.c
index 708988a..abb1165 100644
--- a/builtin/check-attr.c
+++ b/builtin/check-attr.c
@@ -92,6 +92,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
 	struct git_attr_check *check;
 	int cnt, i, doubledash, filei;
 
+	git_config(git_default_config, NULL);
+
 	argc = parse_options(argc, argv, prefix, check_attr_options,
 			     check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
 
diff --git a/cache.h b/cache.h
index 607c2ea..8d95fb2 100644
--- a/cache.h
+++ b/cache.h
@@ -589,6 +589,7 @@ extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern const char *apply_default_ignorewhitespace;
+extern const char *git_attributes_file;
 extern int zlib_compression_level;
 extern int core_compression_level;
 extern int core_compression_seen;
diff --git a/config.c b/config.c
index 4183f80..d3bcaa0 100644
--- a/config.c
+++ b/config.c
@@ -491,6 +491,9 @@ static int git_default_core_config(const char *var, const char *value)
 		return 0;
 	}
 
+	if (!strcmp(var, "core.attributesfile"))
+		return git_config_pathname(&git_attributes_file, var, value);
+
 	if (!strcmp(var, "core.bare")) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		return 0;
diff --git a/environment.c b/environment.c
index e96edcf..d60b73f 100644
--- a/environment.c
+++ b/environment.c
@@ -29,6 +29,7 @@ const char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
 const char *apply_default_ignorewhitespace;
+const char *git_attributes_file;
 int zlib_compression_level = Z_BEST_SPEED;
 int core_compression_level;
 int core_compression_seen;
-- 
1.7.7
Brandon Casey· Oct 6, 2011, 18:22 UTC · lore

[PATCH v3 5/5] attr.c: respect core.ignorecase when matching attribute patterns

From: Brandon Casey <drafnel@gmail.com>

When core.ignorecase is true, the file globs configured in the .gitattributes file should be matched case-insensitively against the paths in the working directory. Let's do so.

Plus, add some tests.

The last set of tests is performed only on a case-insensitive filesystem. Those tests make sure that git handles the case where the .gitignore file resides in a subdirectory and the user supplies a path that does not match the case in the filesystem. In that case^H^H^H^Hsituation, part of the path supplied by the user is effectively interpreted case-insensitively, and part of it is dependent on the setting of core.ignorecase. git should only match the portion of the path below the directory holding the .gitignore file according to the setting of core.ignorecase.

This is also partly future-proofing. Currently, git builds the attr stack based on the path supplied by the user, so we don't have to do anything special (like use strcmp_icase) to handle the parts of that path that don't match the filesystem with respect to case. If git instead built the attr stack by scanning the repository, then the paths in the origin field would not necessarily match the paths supplied by the user. If someone makes a change like that in the future, these tests will notice.

Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
 attr.c                |    5 ++-
 t/t0003-attributes.sh |   60 ++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 62 insertions(+), 3 deletions(-)
Show changes to 2 files +62 −3

attr.c, t/t0003-attributes.sh

diff --git a/attr.c b/attr.c
index 124337d..76b079f 100644
--- a/attr.c
+++ b/attr.c
@@ -11,6 +11,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "attr.h"
+#include "dir.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -631,7 +632,7 @@ static int path_matches(const char *pathname, int pathlen,
 		/* match basename */
 		const char *basename = strrchr(pathname, '/');
 		basename = basename ? basename + 1 : pathname;
-		return (fnmatch(pattern, basename, 0) == 0);
+		return (fnmatch_icase(pattern, basename, 0) == 0);
 	}
 	/*
 	 * match with FNM_PATHNAME; the pattern has base implicitly
@@ -645,7 +646,7 @@ static int path_matches(const char *pathname, int pathlen,
 		return 0;
 	if (baselen != 0)
 		baselen++;
-	return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+	return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
 }
 
 static int macroexpand_one(int attr_nr, int rem);
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index ae2f1da..47a70c4 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -9,7 +9,7 @@ attr_check () {
 	path="$1"
 	expect="$2"
 
-	git check-attr test -- "$path" >actual 2>err &&
+	git $3 check-attr test -- "$path" >actual 2>err &&
 	echo "$path: test: $2" >expect &&
 	test_cmp expect actual &&
 	test_line_count = 0 err
@@ -27,6 +27,7 @@ test_expect_success 'setup' '
 		echo "onoff test -test"
 		echo "offon -test test"
 		echo "no notest"
+		echo "A/e/F test=A/e/F"
 	) >.gitattributes &&
 	(
 		echo "g test=a/g" &&
@@ -93,6 +94,63 @@ test_expect_success 'attribute test' '
 
 '
 
+test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
+
+	test_must_fail attr_check F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
+	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check NO unspecified "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+
+'
+
+test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' '
+
+	attr_check F f "-c core.ignorecase=1" &&
+	attr_check a/F f "-c core.ignorecase=1" &&
+	attr_check a/c/F f "-c core.ignorecase=1" &&
+	attr_check a/G a/g "-c core.ignorecase=1" &&
+	attr_check a/B/g a/b/g "-c core.ignorecase=1" &&
+	attr_check a/b/G a/b/g "-c core.ignorecase=1" &&
+	attr_check a/b/H a/b/h "-c core.ignorecase=1" &&
+	attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check oNoFf unset "-c core.ignorecase=1" &&
+	attr_check oFfOn set "-c core.ignorecase=1" &&
+	attr_check NO unspecified "-c core.ignorecase=1" &&
+	attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check a/b/d/YES unspecified "-c core.ignorecase=1" &&
+	attr_check a/E/f "A/e/F" "-c core.ignorecase=1"
+
+'
+
+test_expect_success 'check whether FS is case-insensitive' '
+	mkdir junk &&
+	echo good >junk/CamelCase &&
+	echo bad >junk/camelcase &&
+	if test "$(cat junk/CamelCase)" != good
+	then
+		test_set_prereq CASE_INSENSITIVE_FS
+	fi
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
+	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check A/b/h a/b/h "-c core.ignorecase=0" &&
+	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
+	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
+'
+
 test_expect_success 'unnormalized paths' '
 
 	attr_check ./f f &&
-- 
1.7.7
Michael Haggerty· Oct 9, 2011, 15:16 UTC · re: Brandon Casey · lore

Re: [PATCH v3 5/5] attr.c: respect core.ignorecase when matching attribute patterns

On 10/06/2011 08:22 PM, Brandon Casey wrote:
Show 8 quoted lines
> The last set of tests is performed only on a case-insensitive filesystem.
> Those tests make sure that git handles the case where the .gitignore file
> resides in a subdirectory and the user supplies a path that does not match
> the case in the filesystem.  In that case^H^H^H^Hsituation, part of the
> path supplied by the user is effectively interpreted case-insensitively,
> and part of it is dependent on the setting of core.ignorecase.  git should
> only match the portion of the path below the directory holding the
> .gitignore file according to the setting of core.ignorecase.

Isn't this a rather perverse scenario? It is hard to imagine that anybody *wants* part of a single filename to be matched case-insensitively and another part to be matched case-sensitively. ISTM that a person who is using a case-insensitive filesystem and has core-ignorecase=false is (1) a glutton for punishment and (2) probably very careful to make sure that the case of all pathnames is correct. So such a person is not likely to benefit from this schizophrenic behavior.

> [...]  If git instead built the attr
> stack by scanning the repository, then the paths in the origin field would
> not necessarily match the paths supplied by the user.  If someone makes a
> change like that in the future, these tests will notice.

Your decision to treat path names as partly case-insensitive will make this kind of improvement considerably more complicated.

Therefore, weighing the negligible benefit of declaring this schizophrenic behavior against the potential big pain of remaining backwards-compatible with this behavior, I suggest that we either (1) declare that when core.ignorecase=false then the *whole* filenames (including the path leading up to the .gitignore file) must match case-sensitively, or (2) declare the behavior in this situation to be undefined so that nobody thinks they should depend on it.

But given that I'm a potential implementer but not a potential Windows user, perhaps my judgment is biased...

Michael
-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
Brandon Casey· Oct 10, 2011, 18:01 UTC · re: Michael Haggerty · lore

Re: [PATCH v3 5/5] attr.c: respect core.ignorecase when matching attribute patterns

On Sun, Oct 9, 2011 at 10:16 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
Show 11 quoted lines
> On 10/06/2011 08:22 PM, Brandon Casey wrote:
>> The last set of tests is performed only on a case-insensitive filesystem.
>> Those tests make sure that git handles the case where the .gitignore file
>> resides in a subdirectory and the user supplies a path that does not match
>> the case in the filesystem.  In that case^H^H^H^Hsituation, part of the
>> path supplied by the user is effectively interpreted case-insensitively,
>> and part of it is dependent on the setting of core.ignorecase.  git should
>> only match the portion of the path below the directory holding the
>> .gitignore file according to the setting of core.ignorecase.
>
> Isn't this a rather perverse scenario?
No argument here. :)
Show 14 quoted lines
> It is hard to imagine that
> anybody *wants* part of a single filename to be matched
> case-insensitively and another part to be matched case-sensitively.
> ISTM that a person who is using a case-insensitive filesystem and has
> core-ignorecase=false is (1) a glutton for punishment and (2) probably
> very careful to make sure that the case of all pathnames is correct.  So
> such a person is not likely to benefit from this schizophrenic behavior.
>
>> [...]  If git instead built the attr
>> stack by scanning the repository, then the paths in the origin field would
>> not necessarily match the paths supplied by the user.  If someone makes a
>> change like that in the future, these tests will notice.
>
> Your decision to treat path names as partly case-insensitive...

You are giving more credit to this patch than it deserves. It really doesn't do much. It's not a design decision that I made to treat path names as case-insensitive. That is a consequence of using a case-insensitive file system. When you give git the path A/b/c and there exists a/.gitattributes, then on a case insensitive file system git will try to read A/.gitattributes and the filesystem will return a handle for a/.gitattributes and git won't be able to tell the difference. git will then apply the rules in the file to the paths below a/ even when the path is supplied like A/. So, at the moment, regardless of the setting of core.ignorecase, the path above a .gitignore file is treated as case-insensitive on a case-insensitive filesystem.

The purpose of the tests are primarily to ensure that nothing special needs to be done concerning paths leading up to a directory containing a .gitignore file in the core.ignorecase=1 case. For example, it is _not_ necessary to use strncmp_icase instead of strncmp on the leading portion of the path.

What I meant when I said "If someone makes a change like that in the future, these tests will notice", is that they will notice that they must now use strncmp_icase when comparing the portion of the path above the .gitattributes file.

Show 9 quoted lines
> will make
> this kind of improvement considerably more complicated.
> Therefore, weighing the negligible benefit of declaring this
> schizophrenic behavior against the potential big pain of remaining
> backwards-compatible with this behavior, I suggest that we either (1)
> declare that when core.ignorecase=false then the *whole* filenames
> (including the path leading up to the .gitignore file) must match
> case-sensitively, or (2) declare the behavior in this situation to be
> undefined so that nobody thinks they should depend on it.

I do not plan to make git treat leading paths case-sensitively on a case-insensitive file system when core.ignorecase=0 any more than I plan to make git treat leading paths case-insensitively on a case-sensitive file system when core.ignorecase=1. For justification, I refer back to your original comment about perversion. :)

So, #2 gets my vote.

Maybe my commit message is not clear that it is describing the current behavior and not defining it. Instead of

   git should only match the portion of the path below the directory
   holding the .gitignore file according to the setting of
   core.ignorecase.
maybe I should say
    git will currently only match the portion of the path...

I could also remove the following test from the CASE_INSENSITIVE_FS tests since it is really a dontcare:

   attr_check A/b/h a/b/h "-c core.ignorecase=0"

We don't care what happens when the user supplies A/b/h and a/b/h exists on disk when core.ignorecase=0, we only care that A/b/h is interpreted correctly when core.ignorecase=1.

-Brandon
Michael Haggerty· Oct 11, 2011, 03:44 UTC · re: Brandon Casey · lore

Re: [PATCH v3 5/5] attr.c: respect core.ignorecase when matching attribute patterns

On 10/10/2011 08:01 PM, Brandon Casey wrote:
Show 20 quoted lines
> On Sun, Oct 9, 2011 at 10:16 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
> Maybe my commit message is not clear that it is describing the current
> behavior and not defining it.  Instead of
> 
>    git should only match the portion of the path below the directory
>    holding the .gitignore file according to the setting of
>    core.ignorecase.
> 
> maybe I should say
> 
>     git will currently only match the portion of the path...
> 
> I could also remove the following test from the CASE_INSENSITIVE_FS
> tests since it is really a dontcare:
> 
>    attr_check A/b/h a/b/h "-c core.ignorecase=0"
> 
> We don't care what happens when the user supplies A/b/h and a/b/h
> exists on disk when core.ignorecase=0, we only care that A/b/h is
> interpreted correctly when core.ignorecase=1.
Sounds good to me.

Thanks, Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
Brandon Casey· Oct 11, 2011, 15:53 UTC · re: Michael Haggerty · lore

[PATCH v4] attr.c: respect core.ignorecase when matching attribute patterns

From: Brandon Casey <drafnel@gmail.com>

When core.ignorecase is true, the file globs configured in the .gitattributes file should be matched case-insensitively against the paths in the working directory. Let's do so.

Plus, add some tests.

The last set of tests is performed only on a case-insensitive filesystem. Those tests make sure that git handles the case where the .gitignore file resides in a subdirectory and the user supplies a path that does not match the case in the filesystem. In that case^H^H^H^Hsituation, part of the path supplied by the user is effectively interpreted case-insensitively, and part of it is dependent on the setting of core.ignorecase. git will currently only match the portion of the path below the directory holding the .gitignore file according to the setting of core.ignorecase.

This is also partly future-proofing. Currently, git builds the attr stack based on the path supplied by the user, so we don't have to do anything special (like use strcmp_icase) to handle the parts of that path that don't match the filesystem with respect to case. If git instead built the attr stack by scanning the repository, then the paths in the origin field would not necessarily match the paths supplied by the user. If someone makes a change like that in the future, these tests will notice.

Signed-off-by: Brandon Casey <drafnel@gmail.com>
---
On Mon, Oct 10, 2011 at 10:44 PM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
Show 23 quoted lines
> On 10/10/2011 08:01 PM, Brandon Casey wrote:
>> On Sun, Oct 9, 2011 at 10:16 AM, Michael Haggerty <mhagger@alum.mit.edu> wrote:
>> Maybe my commit message is not clear that it is describing the current
>> behavior and not defining it.  Instead of
>>
>>    git should only match the portion of the path below the directory
>>    holding the .gitignore file according to the setting of
>>    core.ignorecase.
>>
>> maybe I should say
>>
>>     git will currently only match the portion of the path...
>>
>> I could also remove the following test from the CASE_INSENSITIVE_FS
>> tests since it is really a dontcare:
>>
>>    attr_check A/b/h a/b/h "-c core.ignorecase=0"
>>
>> We don't care what happens when the user supplies A/b/h and a/b/h
>> exists on disk when core.ignorecase=0, we only care that A/b/h is
>> interpreted correctly when core.ignorecase=1.
>
> Sounds good to me.
Ok, here it is.

Minor tweak which, hopefully, adds clarity to the commit message and removes an unnecessary test.

On top of bc/attr-ignore-case^ 64589a03a8ffb3eb4fb2ff8f416ff638a9aaa439.
-Brandon
 attr.c                |    5 ++-
 t/t0003-attributes.sh |   59 ++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 61 insertions(+), 3 deletions(-)
Show changes to 2 files +61 −3

attr.c, t/t0003-attributes.sh

diff --git a/attr.c b/attr.c
index 124337d..76b079f 100644
--- a/attr.c
+++ b/attr.c
@@ -11,6 +11,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "attr.h"
+#include "dir.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -631,7 +632,7 @@ static int path_matches(const char *pathname, int pathlen,
 		/* match basename */
 		const char *basename = strrchr(pathname, '/');
 		basename = basename ? basename + 1 : pathname;
-		return (fnmatch(pattern, basename, 0) == 0);
+		return (fnmatch_icase(pattern, basename, 0) == 0);
 	}
 	/*
 	 * match with FNM_PATHNAME; the pattern has base implicitly
@@ -645,7 +646,7 @@ static int path_matches(const char *pathname, int pathlen,
 		return 0;
 	if (baselen != 0)
 		baselen++;
-	return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+	return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
 }
 
 static int macroexpand_one(int attr_nr, int rem);
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index ae2f1da..6946c4b 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -9,7 +9,7 @@ attr_check () {
 	path="$1"
 	expect="$2"
 
-	git check-attr test -- "$path" >actual 2>err &&
+	git $3 check-attr test -- "$path" >actual 2>err &&
 	echo "$path: test: $2" >expect &&
 	test_cmp expect actual &&
 	test_line_count = 0 err
@@ -27,6 +27,7 @@ test_expect_success 'setup' '
 		echo "onoff test -test"
 		echo "offon -test test"
 		echo "no notest"
+		echo "A/e/F test=A/e/F"
 	) >.gitattributes &&
 	(
 		echo "g test=a/g" &&
@@ -93,6 +94,62 @@ test_expect_success 'attribute test' '
 
 '
 
+test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
+
+	test_must_fail attr_check F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+	test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
+	test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+	attr_check NO unspecified "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
+	test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
+
+'
+
+test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' '
+
+	attr_check F f "-c core.ignorecase=1" &&
+	attr_check a/F f "-c core.ignorecase=1" &&
+	attr_check a/c/F f "-c core.ignorecase=1" &&
+	attr_check a/G a/g "-c core.ignorecase=1" &&
+	attr_check a/B/g a/b/g "-c core.ignorecase=1" &&
+	attr_check a/b/G a/b/g "-c core.ignorecase=1" &&
+	attr_check a/b/H a/b/h "-c core.ignorecase=1" &&
+	attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check oNoFf unset "-c core.ignorecase=1" &&
+	attr_check oFfOn set "-c core.ignorecase=1" &&
+	attr_check NO unspecified "-c core.ignorecase=1" &&
+	attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check a/b/d/YES unspecified "-c core.ignorecase=1" &&
+	attr_check a/E/f "A/e/F" "-c core.ignorecase=1"
+
+'
+
+test_expect_success 'check whether FS is case-insensitive' '
+	mkdir junk &&
+	echo good >junk/CamelCase &&
+	echo bad >junk/camelcase &&
+	if test "$(cat junk/CamelCase)" != good
+	then
+		test_set_prereq CASE_INSENSITIVE_FS
+	fi
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
+	test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+	test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+	attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
+	attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+	attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
+'
+
 test_expect_success 'unnormalized paths' '
 
 	attr_check ./f f &&
-- 
1.7.7.138.g7f41b6
Junio C Hamano· Oct 11, 2011, 16:54 UTC · re: Brandon Casey · lore

Re: [PATCH v4] attr.c: respect core.ignorecase when matching attribute patterns

Brandon Casey <casey@nrlssc.navy.mil> writes:
Show 6 quoted lines
> ...  Currently, git builds the attr stack
> based on the path supplied by the user, so we don't have to do anything
> special (like use strcmp_icase) to handle the parts of that path that don't
> match the filesystem with respect to case.  If git instead built the attr
> stack by scanning the repository, then the paths in the origin field would
> not necessarily match the paths supplied by the user.

I find this description somewhat misleading. "check-attr" at the plumbing level does take full path from the end user, but a common thing Git does is to ask the system to learn the prefix to the current directory with getcwd(3) append what fill_directory() enumerates as matching a pathspec given by the user with readdir(3) to the prefix to form the full path, and then feed that full path to git_check_attr().

Without anybody changing anything, we already do build the attr stack by "scanning the repository" in that case, no?

Brandon Casey· Oct 12, 2011, 20:18 UTC · re: Junio C Hamano · lore

Re: [PATCH v4] attr.c: respect core.ignorecase when matching attribute patterns

On 10/11/2011 11:54 AM, Junio C Hamano wrote:
Show 18 quoted lines
> Brandon Casey <casey@nrlssc.navy.mil> writes:
> 
>> ...  Currently, git builds the attr stack
>> based on the path supplied by the user, so we don't have to do anything
>> special (like use strcmp_icase) to handle the parts of that path that don't
>> match the filesystem with respect to case.  If git instead built the attr
>> stack by scanning the repository, then the paths in the origin field would
>> not necessarily match the paths supplied by the user.
> 
> I find this description somewhat misleading. "check-attr" at the plumbing
> level does take full path from the end user, but a common thing Git does
> is to ask the system to learn the prefix to the current directory with
> getcwd(3) append what fill_directory() enumerates as matching a pathspec
> given by the user with readdir(3) to the prefix to form the full path, and
> then feed that full path to git_check_attr().
> 
> Without anybody changing anything, we already do build the attr stack by
> "scanning the repository" in that case, no?

Well, kind of. What I meant by "scanning the repository", was having two separate mechanisms: one to build the attr stack, and one to scan it to get the attributes. Right now, the two operations are tied together and the stack is built as needed, and it is built using the same path string that the scan operation will use for checking for attributes. So, the leading paths will match.

When I wrote that commit message, I really was only thinking about a user-supplied path, but the focus should be on prepare_attr_stack(). The reason the leading paths to a .gitattributes file will necessarily match is because the attr stack is built using the path supplied to prepare_attr_stack(), and the same path string is used when scanning the stack to check for attributes. So each path that is supplied, regardless of whether its case matches the case in the file system or in the repository, will have an entry in the attr stack.

Maybe that last paragraph in the commit message should just be dropped. I think the preceding paragraph explains the purpose of the tests, and this last one doesn't really add any value.

Do you want me to resubmit or can you fix it up? I ask not because I am too lazy to do 'commit --amend' myself, but because you may prefer to receive one less patch in your inbox if you can easily apply the change yourself.

-Brandon
Junio C Hamano· Oct 12, 2011, 20:24 UTC · re: Brandon Casey · lore

Re: [PATCH v4] attr.c: respect core.ignorecase when matching attribute patterns

Brandon Casey <casey@nrlssc.navy.mil> writes:
Show 5 quoted lines
> On 10/11/2011 11:54 AM, Junio C Hamano wrote:
> ...
> Maybe that last paragraph in the commit message should just be dropped.
> I think the preceding paragraph explains the purpose of the tests, and
> this last one doesn't really add any value.

I wasn't saying the description was wrong per-se. It only makes difference for "git check-attr" users, but it still _does_ make difference for them, I think. So I kept the paragraph in the end.

> Do you want me to resubmit or can you fix it up?  I ask not because I
> am too lazy to do 'commit --amend' myself, but because you may prefer
> to receive one less patch in your inbox if you can easily apply the
> change yourself.

It doesn't make much difference to me, as long as I don't forget a simple and no-brainer amend I am supposed to do myself ;-).

Thanks.
Junio C Hamano· Oct 11, 2011, 00:00 UTC · re: Michael Haggerty · lore

Re: [PATCH v3 5/5] attr.c: respect core.ignorecase when matching attribute patterns

Michael Haggerty <mhagger@alum.mit.edu> writes:
Show 13 quoted lines
> On 10/06/2011 08:22 PM, Brandon Casey wrote:
>> The last set of tests is performed only on a case-insensitive filesystem.
>> Those tests make sure that git handles the case where the .gitignore file
>> resides in a subdirectory and the user supplies a path that does not match
>> the case in the filesystem.  In that case^H^H^H^Hsituation, part of the
>> path supplied by the user is effectively interpreted case-insensitively,
>> and part of it is dependent on the setting of core.ignorecase.  git should
>> only match the portion of the path below the directory holding the
>> .gitignore file according to the setting of core.ignorecase.
>
> ... It is hard to imagine that
> anybody *wants* part of a single filename to be matched
> case-insensitively and another part to be matched case-sensitively.

That is not necessarily coming from the user's wish. When a command that takes a pathspec from the user is run from a subdirectory, almost always the output from $(git rev-parse --show-prefix) is prefixed to them. This value is read from the filesystem, and unless on a system whose readdir() may lie to us, we do not _have_ to ignore the case of this part of the substring of a resulting pathspec element to get a successful match, even though we _do_ want to match the user-supplied part case insensitively if/when the user says he wants us to with core.ignorecase.

Having said that, it may not hurt in practice if we matched the prefix part case insensitvely, because prefix=foobar/ obtained on a filesystem where core.ignorecase is true would mean there won't be FooBar/ and other case-permuted names on the filesystem at the same time.

But of course I may be missing something...

← back to recent threads