Skip to contentSkip to conversation

Provide a dirname() function when NO_LIBGEN_H=YesPlease

PATCH In review Johannes Schindelin 8 participants 63 messages Jun 15, 2016 1 message not in the archive

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Provide a dirname() function when NO_LIBGEN_H=YesPlease

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---

I stumbled over the compile warning when upgrading Git for Windows to 2.6.0. There was a left-over NO_LIBGEN_H=YesPlease (which we no longer need in Git for Windows 2.x), but it did point to the fact that we use dirname() in builtin/am.c now, so we better have a fall-back implementation for platforms without libgen.h.

I tested this implementation a bit, but I still would appreciate a few eye-balls to go over it.

 compat/basename.c | 26 ++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 28 insertions(+)
compat/basename.c +26 -0
index d8f8a3c..10dba38 100644
@@ -13,3 +13,29 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash, c;
+
+	/* Skip over the disk name in MSDOS pathnames. */
+	if (has_dos_drive_prefix(p))
+		p += 2;
+	/* POSIX.1-2001 says dirname("/") should return "/" */
+	slash = is_dir_sep(*p) ? ++p : NULL;
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+
+	if (!slash)
+		return ".";
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +2 -0
index f649e81..8b01aa5 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
Signature · 1 line
2.5.3.windows.1.3.gc322723
#1 lore raw

Hi Johannes,

Quoted 6 lines · Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Yes, many moons ago (on my old 32-bit laptop) when I was still 'working' with MinGW I noticed this same thing while looking into providing a win32 emulation of unix sockets. So, I had to look into this at the same time. Since this didn't progress, I didn't mention the libgen issue.

Anyway, I still have a 'test-libgen.c' file (attached) from back then that contains some tests. I don't quite recall what the final state of this code was, but it was intended to test _existing_ libgen implementations as well as provide a 'git' version which would work on MinGW, cygwin and linux. Note that some of the existing implementations didn't all agree on what the tests should report! I don't remember if I looked at the POSIX spec or not.

So, I don't know how useful it will be - if nothing else, there are some tests! :-D

HTH

Ramsay Jones

Quoted 61 lines

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---

I stumbled over the compile warning when upgrading Git for Windows to 2.6.0. There was a left-over NO_LIBGEN_H=YesPlease (which we no longer need in Git for Windows 2.x), but it did point to the fact that we use dirname() in builtin/am.c now, so we better have a fall-back implementation for platforms without libgen.h.

I tested this implementation a bit, but I still would appreciate a few eye-balls to go over it.

 compat/basename.c | 26 ++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 28 insertions(+)
compat/basename.c +26 -0
index d8f8a3c..10dba38 100644
@@ -13,3 +13,29 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash, c;
+
+	/* Skip over the disk name in MSDOS pathnames. */
+	if (has_dos_drive_prefix(p))
+		p += 2;
+	/* POSIX.1-2001 says dirname("/") should return "/" */
+	slash = is_dir_sep(*p) ? ++p : NULL;
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+
+	if (!slash)
+		return ".";
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +0 -0
index f649e81..8b01aa5 100644

git-compat-util.h struct itimerval { · line 260

Quoted 8 lines
git-compat-util.h +2 -0
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifndef NO_LIBGEN_H

# include <libgen.h>
#endif

struct test_data {
char *from; /* input: transform from this ... */
char *to; /* output: ... to this. */
};

#ifdef NO_LIBGEN_H

#if defined(__MINGW32__) || defined(_MSC_VER)
#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
#else
#define has_dos_drive_prefix(path) 0
#define is_dir_sep(c) ((c) == '/')
#endif

#define basename gitbasename
#define dirname gitdirname

char *gitbasename (char *path)

{
	char *p;

	if (!path || !*path)
		return ".";
	/* skip drive designator, if any */
	if (has_dos_drive_prefix(path))
		path += 2;
	if (!*path)
		return ".";
	/* trim trailing directory separators */
	p = path + strlen(path) - 1;
	while (is_dir_sep(*p)) {
		if (p == path)
			return path;
		*p-- = '\0';
	}
	/* find begining of last path component */
	while (p >= path && !is_dir_sep(*p))
		p--;
	return p + 1;
}

char *gitdirname(char *path)

{
	char *p, *start;

	if (!path || !*path)
		return ".";
	start = path;
	/* skip drive designator, if any */
	if (has_dos_drive_prefix(path))
		start += 2;
	/* check for // */
	if (strcmp(start, "//") == 0)
		return path;
	/* check for \\ */
	if (is_dir_sep('\\') && strcmp(start, "\\\\") == 0)
		return path;
	/* trim trailing directory separators */
	p = path + strlen(path) - 1;
	while (is_dir_sep(*p)) {
		if (p == start)
			return path;
		*p-- = '\0';
	}
	/* find begining of last path component */
	while (p >= start && !is_dir_sep(*p))
		p--;
	/* terminate dirname */
	if (p < start) {
		p = start;
		*p++ = '.';
	} else if (p == start)
		p++;
	*p = '\0';
	return path;
}

#endif

static int test_basename(void)

{
	static struct test_data t[] = {

		/* --- POSIX type paths --- */
		{ NULL,              "."    },
		{ "",                "."    },
		{ ".",               "."    },
		{ "..",              ".."   },
		{ "/",               "/"    },
#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
		{ "//",              "//"   },
		{ "///",             "//"   },
		{ "////",            "//"   },
#else
		{ "//",              "/"    },
		{ "///",             "/"    },
		{ "////",            "/"    },
#endif
		{ "usr",             "usr"  },
		{ "/usr",            "usr"  },
		{ "/usr/",           "usr"  },
		{ "/usr//",          "usr"  },
		{ "/usr/lib",        "lib"  },
		{ "usr/lib",         "lib"  },
		{ "usr/lib///",      "lib"  },

#if defined(__MINGW32__) || defined(_MSC_VER)

		/* --- win32 type paths --- */
		{ "\\usr",           "usr"  },
		{ "\\usr\\",         "usr"  },
		{ "\\usr\\\\",       "usr"  },
		{ "\\usr\\lib",      "lib"  },
		{ "usr\\lib",        "lib"  },
		{ "usr\\lib\\\\\\",  "lib"  },
		{ "C:/usr",          "usr"  },
		{ "C:/usr",          "usr"  },
		{ "C:/usr/",         "usr"  },
		{ "C:/usr//",        "usr"  },
		{ "C:/usr/lib",      "lib"  },
		{ "C:usr/lib",       "lib"  },
		{ "C:usr/lib///",    "lib"  },
		{ "C:",              "."    },
		{ "C:a",             "a"    },
		{ "C:/",             "/"    },
		{ "C:///",           "/"    },
#if defined(NO_LIBGEN_H)
		{ "\\",              "\\"   },
		{ "\\\\",            "\\"   },
		{ "\\\\\\",          "\\"   },

#else

		/* win32 platform variations: */
#if defined(__MINGW32__)
		{ "\\",              "/"    },
		{ "\\\\",            "/"    },
		{ "\\\\\\",          "/"    },

#endif

#if defined(_MSC_VER)
		{ "\\",              "\\"   },
		{ "\\\\",            "\\"   },
		{ "\\\\\\",          "\\"   },

#endif

#endif

#endif
		{ NULL,              "."    }
	};
	static char input[1024];
	char *from, *to;
	int i, failed = 0;

	for (i = 0; i < sizeof(t)/sizeof(t[0]); i++) {
		from = NULL;
		if (t[i].from) {
			strcpy(input, t[i].from);
			from = input;
		}
		to = basename(from);
		if (strcmp(to, t[i].to) != 0) {
			fprintf(stderr, "FAIL: basename(%s) => '%s' != '%s'\n",
				t[i].from, to, t[i].to);
			failed++;
		}
	}
	return failed != 0;
}

static int test_dirname(void)

{
	static struct test_data t[] = {

		/* --- POSIX type paths --- */
		{ NULL,              "."      },
		{ "",                "."      },
		{ ".",               "."      },
		{ "..",              "."      },
		{ "/",               "/"      },
		{ "//",              "//"     },
#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
		{ "///",             "//"     },
		{ "////",            "//"     },
#else
		{ "///",             "/"      },
		{ "////",            "/"      },
#endif
		{ "usr",             "."      },
		{ "/usr",            "/"      },
		{ "/usr/",           "/"      },
		{ "/usr//",          "/"      },
		{ "/usr/lib",        "/usr"   },
		{ "usr/lib",         "usr"    },
		{ "usr/lib///",      "usr"    },

#if defined(__MINGW32__) || defined(_MSC_VER)

		/* --- win32 type paths --- */
		{ "\\",              "\\"     },
		{ "\\\\",            "\\\\"   },
		{ "\\usr",           "\\"     },
		{ "\\usr\\",         "\\"     },
		{ "\\usr\\\\",       "\\"     },
		{ "\\usr\\lib",      "\\usr"  },
		{ "usr\\lib",        "usr"    },
		{ "usr\\lib\\\\\\",  "usr"    },
		{ "C:a",             "C:."    },
		{ "C:/",             "C:/"    },
		{ "C:///",           "C:/"    },
		{ "C:/usr",          "C:/"    },
		{ "C:/usr/",         "C:/"    },
		{ "C:/usr//",        "C:/"    },
		{ "C:/usr/lib",      "C:/usr" },
		{ "C:usr/lib",       "C:usr"  },
		{ "C:usr/lib///",    "C:usr"  },
		{ "\\\\\\",          "\\"     },
		{ "\\\\\\\\",        "\\"     },
#if defined(NO_LIBGEN_H)
		{ "C:",              "C:."    },

#else

		/* win32 platform variations: */
#if defined(__MINGW32__)
		/* the following is clearly wrong ... */
		{ "C:",              "."      },

#endif

#if defined(_MSC_VER)
		{ "C:",              "C:."    },

#endif

#endif

#endif
		{ NULL,              "."      }
	};
	static char input[1024];
	char *from, *to;
	int i, failed = 0;

	for (i = 0; i < sizeof(t)/sizeof(t[0]); i++) {
		from = NULL;
		if (t[i].from) {
			strcpy(input, t[i].from);
			from = input;
		}
		to = dirname(from);
		if (strcmp(to, t[i].to) != 0) {
			fprintf(stderr, "FAIL: dirname(%s) => '%s' != '%s'\n",
				t[i].from, to, t[i].to);
			failed++;
		}
	}
	return failed != 0;
}

int main(int argc, char **argv)

{
	if (argc == 2 && !strcmp(argv[1], "basename"))
		return test_basename();

	if (argc == 2 && !strcmp(argv[1], "dirname"))
		return test_dirname();

	fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
		argv[1] ? argv[1] : "(there was none)");
	return 1;
}
#2 lore raw

Hi Ramsay,

Quoted 9 lines · Ramsay Jones
Quoted 7 lines · Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function:
credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Quoted 7 lines

Yes, many moons ago (on my old 32-bit laptop) when I was still 'working' with MinGW I noticed this same thing while looking into providing a win32 emulation of unix sockets. So, I had to look into this at the same time. Since this didn't progress, I didn't mention the libgen issue.

Anyway, I still have a 'test-libgen.c' file (attached) from back then that contains some tests.

Awesome. Thank you! I integrated the tests back into test-path-utils.c (from where the framework clearly came) and made it part of the regression test suite in the upcoming v2.

Ciao,
Dscho

#3 lore raw
In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin

I stumbled over the compile warning when upgrading Git for Windows to 2.6.0. There was a left-over NO_LIBGEN_H=YesPlease (which we no longer need in Git for Windows 2.x), but it did point to the fact that we use dirname() in builtin/am.c now, so we better have a fall-back implementation for platforms without libgen.h.

Thanks for being careful.

Quoted 14 lines

I tested this implementation a bit, but I still would appreciate a few eye-balls to go over it.

 compat/basename.c | 26 ++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 28 insertions(+)
compat/basename.c +0 -0
index d8f8a3c..10dba38 100644
@@ -13,3 +13,29 @@ char *gitbasename (char *path)
 	}

compat/basename.c char *gitbasename (char *path) · line 23

Quoted 10 lines
compat/basename.c +8 -0
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash, c;
+
+	/* Skip over the disk name in MSDOS pathnames. */
+	if (has_dos_drive_prefix(p))
+		p += 2;

Not a new problem, but many callers of has_dos_drive_prefix() hardcodes that "2" in various forms. I wonder if this is something we should relieve callers of by tweaking the semantics of it, e.g. by returning 2 (or howmanyever bytes should be skipped) from the function, changing it to skip_dos_drive_prefix(&p), etc.

compat/basename.c char *gitbasename (char *path) · line 26

Quoted 3 lines
compat/basename.c +3 -0
+	/* POSIX.1-2001 says dirname("/") should return "/" */
+	slash = is_dir_sep(*p) ? ++p : NULL;
+	while ((c = *(p++)))

I am confused by this. What is the invariant on 'p' at the beginning of the body of this while loop in each iteration?

Inside the body, p skips over dir-sep characters, so p must point at the byte past the last run of slashes?

If that is the invariant, upon entry, shouldn't the initialization of "slash" be skipping over all slashes, not just the first one, when the input is "///foo", for example? Instead the above skips '/' and sets slash to the byte past the first '/' (which is OK because you want to NUL-terminate to remove "//foo" from the input) but does not move p to 'f', so the invariant is not "p must point at the byte past the last run of slashes".

compat/basename.c char *gitbasename (char *path) · line 35

Quoted 9 lines
compat/basename.c +9 -0
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}

I would have expected the function to scan from the end/right/tail.

Quoted 18 lines
compat/basename.c +5 -0
+	if (!slash)
+		return ".";
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +2 -0
index f649e81..8b01aa5 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
#4 lore raw

Hi Junio,

Quoted 16 lines · Junio C Hamano
Quoted 14 lines · Johannes Schindelin
compat/basename.c +6 -0
index d8f8a3c..10dba38 100644
@@ -13,3 +13,29 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash, c;
+
+	/* Skip over the disk name in MSDOS pathnames. */
Quoted 8 lines

compat/basename.c char *gitbasename (char *path) · line 23

Quoted 2 lines
compat/basename.c +2 -0
+	if (has_dos_drive_prefix(p))
+		p += 2;

Not a new problem, but many callers of has_dos_drive_prefix() hardcodes that "2" in various forms. I wonder if this is something we should relieve callers of by tweaking the semantics of it, e.g. by returning 2 (or howmanyever bytes should be skipped) from the function, changing it to skip_dos_drive_prefix(&p), etc.

In the upcoming v2, this is addressed.

Quoted 6 lines

compat/basename.c char *gitbasename (char *path) · line 26

Quoted 3 lines
compat/basename.c +3 -0
+	/* POSIX.1-2001 says dirname("/") should return "/" */
+	slash = is_dir_sep(*p) ? ++p : NULL;
+	while ((c = *(p++)))

I am confused by this. What is the invariant on 'p' at the beginning of the body of this while loop in each iteration?

The idea was that 'p' looks at whatever is the next character. And 'slash' records the location of the latest slash we have seen (to be overwritten with a '\0' to terminate the dirname).

Since '/' should not be shortened to the empty string, I special-cased absolute paths to actually shift the 'slash' to one byte later. A little dirty, but it worked.

Except that Ramsay's tests pointed out that I did not even look at what the specs say, and I even fixed basename() in the meantime (and I think dirname() looks more readable, but feel free to contradict me there).

Quoted 11 lines

compat/basename.c char *gitbasename (char *path) · line 35

Quoted 9 lines
compat/basename.c +9 -0
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}

I would have expected the function to scan from the end/right/tail.

Why scan twice (once to find the end, then to find the slashes)?

Ciao,
Dscho

#5 lore raw

v2 cover Ensure that we can build without libgen.h

In reply to Johannes Schindelin

This mini series adds a fall-back for the dirname() function that we use e.g. in git-am. This is necessary because not all platforms have a working libgen.h. MSys2 (on which Git for Windows relies) does have a libgen.h, but its basename() implementation is broken, thus we cannot use it.

Johannes Schindelin (4):
Refactor skipping DOS drive prefixes compat/basename: make basename() conform to POSIX Provide a dirname() function when NO_LIBGEN_H=YesPlease
t0060: verify that basename() and dirname() work as expected

 compat/basename.c     |  66 ++++++++++++++++++--
 compat/mingw.c        |  14 ++---
 compat/mingw.h        |  10 ++-
 git-compat-util.h     |  10 +++
 path.c                |  14 ++---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 261 insertions(+), 24 deletions(-)

Interdiff vs v1:

diff --git a/compat/basename.c b/compat/basename.c index 10dba38..0a2ed25 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -1,28 +1,58 @@
#include "../git-compat-util.h"
+#include "../strbuf.h"

/* Adapted from libiberty's basename.c. */
char *gitbasename (char *path)
{
const char *base;
- /* Skip over the disk name in MSDOS pathnames. */
- if (has_dos_drive_prefix(path))
- path += 2;
+
+ if (path)
+ skip_dos_drive_prefix(&path);
+
+ if (!path || !*path)
+ return ".";
+
for (base = path; *path; path++) {
- if (is_dir_sep(*path))
- base = path + 1;
+ if (!is_dir_sep(*path))
+ continue;
+ do {
+ path++;
+ } while (is_dir_sep(*path));
+ if (*path)
+ base = path;
+ else
+ while (--path != base && is_dir_sep(*path))
+ *path = '\0';
}
return (char *)base;
}

char *gitdirname(char *path)
{
- char *p = path, *slash, c;
+ char *p = path, *slash = NULL, c;
+ int dos_drive_prefix;
+
+ if (!p)
+ return ".";

- /* Skip over the disk name in MSDOS pathnames. */
- if (has_dos_drive_prefix(p))
- p += 2;
- /* POSIX.1-2001 says dirname("/") should return "/" */
- slash = is_dir_sep(*p) ? ++p : NULL;
+ if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
+ static struct strbuf buf = STRBUF_INIT;
+
+dot:
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+ return buf.buf;
+ }
+
+ /*
+ * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+ * should return "//", but dirname("///") should return "/" again.
+ */
+ if (is_dir_sep(*p)) {
+ if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+ return path;
+ slash = ++p;
+ }
while ((c = *(p++)))
if (is_dir_sep(c)) {
char *tentative = p - 1;
@@ -35,7 +65,7 @@ char *gitdirname(char *path)
}

if (!slash)
- return ".";
+ goto dot;
*slash = '\0';
return path;
}
diff --git a/compat/mingw.c b/compat/mingw.c
index 5edea29..1b3530a 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -1934,26 +1934,22 @@ pid_t waitpid(pid_t pid, int *status, int options)

int mingw_offset_1st_component(const char *path) {
- int offset = 0;
- if (has_dos_drive_prefix(path))
- offset = 2;
+ char *pos = (char *)path;

/* unc paths */
- else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+ if (!skip_dos_drive_prefix(&pos) &&
+ is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
/* skip server name */
- char *pos = strpbrk(path + 2, "\\/");
+ pos = strpbrk(pos + 2, "\\/");
if (!pos)
return 0; /* Error: malformed unc path */

do {
pos++;
} while (*pos && !is_dir_sep(*pos));
-
- offset = pos - path;
}

- return offset + is_dir_sep(path[offset]);
+ return pos + is_dir_sep(*pos) - path;
}

int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
diff --git a/compat/mingw.h b/compat/mingw.h
index 57ca477..b3e5044 100644
--- a/compat/mingw.h
+++ b/compat/mingw.h
@@ -361,7 +361,15 @@ HANDLE winansi_get_osfhandle(int fd);
* git specific compatibility
*/

-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') +#define has_dos_drive_prefix(path) \
+ (isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+static inline int mingw_skip_dos_drive_prefix(char **path)
+{
+ int ret = has_dos_drive_prefix(*path);
+ *path += ret;
+ return ret;
+}
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
#define is_dir_sep(c) ((c) == '/' || (c) == '\\')
static inline char *mingw_find_last_dir_sep(const char *path)
{
diff --git a/git-compat-util.h b/git-compat-util.h
index 996ee17..94f311a 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -337,6 +337,14 @@ static inline int git_has_dos_drive_prefix(const char *path)
#define has_dos_drive_prefix git_has_dos_drive_prefix
#endif

+#ifndef skip_dos_drive_prefix
+static inline int git_skip_dos_drive_prefix(const char **path)
+{
+ return 0;
+}
+#define skip_dos_drive_prefix git_skip_dos_drive_prefix
+#endif
+
#ifndef is_dir_sep
static inline int git_is_dir_sep(int c)
{
diff --git a/path.c b/path.c
index 3cd155e..8b7e168 100644
--- a/path.c
+++ b/path.c
@@ -782,13 +782,10 @@ const char *relative_path(const char *in, const char *prefix,
else if (!prefix_len)
return in;

- if (have_same_root(in, prefix)) {
+ if (have_same_root(in, prefix))
/* bypass dos_drive, for "c:" is identical to "C:" */
- if (has_dos_drive_prefix(in)) {
- i = 2;
- j = 2;
- }
- } else {
+ i = j = has_dos_drive_prefix(in);
+ else {
return in;
}

@@ -943,11 +940,10 @@ const char *remove_leading_path(const char *in, const char *prefix)
int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) {
char *dst0;
+ int i;

- if (has_dos_drive_prefix(src)) {
+ for (i = has_dos_drive_prefix(src); i > 0; i--)
*dst++ = *src++;
- *dst++ = *src++;
- }
dst0 = dst;

if (is_dir_sep(*src)) {
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 627ef85..f0152a7 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -59,6 +59,9 @@ case $(uname -s) in
;;
esac

+test_expect_success basename 'test-path-utils basename' +test_expect_success dirname 'test-path-utils dirname'
+
norm_path "" ""
norm_path . ""
norm_path ./ ""
diff --git a/test-path-utils.c b/test-path-utils.c
index c67bf65..74e74c9 100644
--- a/test-path-utils.c
+++ b/test-path-utils.c
@@ -39,6 +39,168 @@ static void normalize_argv_string(const char **var, const char *input)
die("Bad value: %s\n", input);
}

+struct test_data {
+ char *from; /* input: transform from this ... */
+ char *to; /* output: ... to this. */
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+ const char *funcname)
+{
+ int failed = 0, i;
+ static char buffer[1024];
+ char *to;
+
+ for (i = 0; data[i].to; i++) {
+ if (!data[i].from)
+ to = func(NULL);
+ else {
+ strcpy(buffer, data[i].from);
+ to = func(buffer);
+ }
+ if (strcmp(to, data[i].to)) {
+ error("FAIL: %s(%s) => '%s' != '%s'\n",
+ funcname, data[i].from, to, data[i].to);
+ failed++;
+ }
+ }
+ return !!failed;
+}
+
+static struct test_data basename_data[] = {
+ /* --- POSIX type paths --- */

 +	{ NULL,              "."    },
 +	{ "",                "."    },
 +	{ ".",               "."    },
 +	{ "..",              ".."   },
 +	{ "/",               "/"    },
 +#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
 +	{ "//",              "//"   },
 +	{ "///",             "//"   },
 +	{ "////",            "//"   },
 +#else
 +	{ "//",              "/"    },
 +	{ "///",             "/"    },
 +	{ "////",            "/"    },
 +#endif
 +	{ "usr",             "usr"  },
 +	{ "/usr",            "usr"  },
 +	{ "/usr/",           "usr"  },
 +	{ "/usr//",          "usr"  },
 +	{ "/usr/lib",        "lib"  },
 +	{ "usr/lib",         "lib"  },
 +	{ "usr/lib///",      "lib"  },
 +
 +#if defined(__MINGW32__) || defined(_MSC_VER)
 +
 +	/* --- win32 type paths --- */
 +	{ "\\usr",           "usr"  },
 +	{ "\\usr\\",         "usr"  },
 +	{ "\\usr\\\\",       "usr"  },
 +	{ "\\usr\\lib",      "lib"  },
 +	{ "usr\\lib",        "lib"  },
 +	{ "usr\\lib\\\\\\",  "lib"  },
 +	{ "C:/usr",          "usr"  },
 +	{ "C:/usr",          "usr"  },
 +	{ "C:/usr/",         "usr"  },
 +	{ "C:/usr//",        "usr"  },
 +	{ "C:/usr/lib",      "lib"  },
 +	{ "C:usr/lib",       "lib"  },
 +	{ "C:usr/lib///",    "lib"  },
 +	{ "C:",              "."    },
 +	{ "C:a",             "a"    },
 +	{ "C:/",             "/"    },
 +	{ "C:///",           "/"    },
 +#if defined(NO_LIBGEN_H)
 +	{ "\\",              "\\"   },
 +	{ "\\\\",            "\\"   },
 +	{ "\\\\\\",          "\\"   },
 +#else
 +
 +	/* win32 platform variations: */
 +#if defined(__MINGW32__)
 +	{ "\\",              "/"    },
 +	{ "\\\\",            "/"    },
 +	{ "\\\\\\",          "/"    },
 +#endif
 +
 +#if defined(_MSC_VER)
 +	{ "\\",              "\\"   },
 +	{ "\\\\",            "\\"   },
 +	{ "\\\\\\",          "\\"   },
 +#endif
 +
 +#endif
 +#endif
 +	{ NULL,              "."    },
 +	{ NULL,              NULL   }
 +};
 +
 +static struct test_data dirname_data[] = {
 +	/* --- POSIX type paths --- */
 +	{ NULL,              "."      },
 +	{ "",                "."      },
 +	{ ".",               "."      },
 +	{ "..",              "."      },
 +	{ "/",               "/"      },
 +	{ "//",              "//"     },
 +#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
 +	{ "///",             "//"     },
 +	{ "////",            "//"     },
 +#else
 +	{ "///",             "/"      },
 +	{ "////",            "/"      },
 +#endif
 +	{ "usr",             "."      },
 +	{ "/usr",            "/"      },
 +	{ "/usr/",           "/"      },
 +	{ "/usr//",          "/"      },
 +	{ "/usr/lib",        "/usr"   },
 +	{ "usr/lib",         "usr"    },
 +	{ "usr/lib///",      "usr"    },
 +
 +#if defined(__MINGW32__) || defined(_MSC_VER)
 +
 +	/* --- win32 type paths --- */
 +	{ "\\",              "\\"     },
 +	{ "\\\\",            "\\\\"   },
 +	{ "\\usr",           "\\"     },
 +	{ "\\usr\\",         "\\"     },
 +	{ "\\usr\\\\",       "\\"     },
 +	{ "\\usr\\lib",      "\\usr"  },
 +	{ "usr\\lib",        "usr"    },
 +	{ "usr\\lib\\\\\\",  "usr"    },
 +	{ "C:a",             "C:."    },
 +	{ "C:/",             "C:/"    },
 +	{ "C:///",           "C:/"    },
 +	{ "C:/usr",          "C:/"    },
 +	{ "C:/usr/",         "C:/"    },
 +	{ "C:/usr//",        "C:/"    },
 +	{ "C:/usr/lib",      "C:/usr" },
 +	{ "C:usr/lib",       "C:usr"  },
 +	{ "C:usr/lib///",    "C:usr"  },
 +	{ "\\\\\\",          "\\"     },
 +	{ "\\\\\\\\",        "\\"     },
 +#if defined(NO_LIBGEN_H)
 +	{ "C:",              "C:."    },
 +#else
 +
 +	/* win32 platform variations: */
 +#if defined(__MINGW32__)
 +	/* the following is clearly wrong ... */
 +	{ "C:",              "."      },
 +#endif
 +
 +#if defined(_MSC_VER)
 +	{ "C:",              "C:."    },
 +#endif
 +
 +#endif
 +#endif
 +	{ NULL,              "."      },
 +	{ NULL,              NULL     }

+};
+
int main(int argc, char **argv)
{
if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -133,6 +295,12 @@ int main(int argc, char **argv)
return 0;
}

+ if (argc == 2 && !strcmp(argv[1], "basename"))
+ return test_function(basename_data, basename, argv[1]);
+
+ if (argc == 2 && !strcmp(argv[1], "dirname"))
+ return test_function(dirname_data, dirname, argv[1]);
+
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
argv[1] ? argv[1] : "(there was none)");
return 1;

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#6 lore raw

PATCH v2 1/4 Refactor skipping DOS drive prefixes

Junio Hamano pointed out that there is an implicit assumption in pretty much all the code calling has_dos_drive_prefix(): it assumes that the DOS drive prefix is always two bytes long.

While this assumption is pretty safe, we can still make the code more readable and less error-prone by introducing a function that skips the DOS drive prefix safely.

While at it, we change the has_dos_drive_prefix() return value: it now returns the number of bytes to be skipped if there is a DOS drive prefix.

---
 compat/basename.c |  4 +---
 compat/mingw.c    | 14 +++++---------
 compat/mingw.h    | 10 +++++++++-
 git-compat-util.h |  8 ++++++++
 path.c            | 14 +++++---------
 5 files changed, 28 insertions(+), 22 deletions(-)
compat/basename.c +1 -3
index d8f8a3c..9f00421 100644
@@ -4,9 +4,7 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	/* Skip over the disk name in MSDOS pathnames. */
-	if (has_dos_drive_prefix(path))
-		path += 2;
+	skip_dos_drive_prefix(&path);
 	for (base = path; *path; path++) {
 		if (is_dir_sep(*path))
 			base = path + 1;
compat/mingw.c +5 -9
index 5edea29..1b3530a 100644
@@ -1934,26 +1934,22 @@ pid_t waitpid(pid_t pid, int *status, int options)
 
 int mingw_offset_1st_component(const char *path)
 {
-	int offset = 0;
-	if (has_dos_drive_prefix(path))
-		offset = 2;
+	char *pos = (char *)path;
 
 	/* unc paths */
-	else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+	if (!skip_dos_drive_prefix(&pos) &&
+			is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 		/* skip server name */
-		char *pos = strpbrk(path + 2, "\\/");
+		pos = strpbrk(pos + 2, "\\/");
 		if (!pos)
 			return 0; /* Error: malformed unc path */
 
 		do {
 			pos++;
 		} while (*pos && !is_dir_sep(*pos));
-
-		offset = pos - path;
 	}
 
-	return offset + is_dir_sep(path[offset]);
+	return pos + is_dir_sep(*pos) - path;
 }
 
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
compat/mingw.h +9 -1
index 57ca477..b3e5044 100644
@@ -361,7 +361,15 @@ HANDLE winansi_get_osfhandle(int fd);
  * git specific compatibility
  */
 
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
+#define has_dos_drive_prefix(path) \
+	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+static inline int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
 {
git-compat-util.h +8 -0
index 2da0a75..0d66f3a 100644
@@ -335,6 +335,14 @@ static inline int git_has_dos_drive_prefix(const char *path)
 #define has_dos_drive_prefix git_has_dos_drive_prefix
 #endif
 
+#ifndef skip_dos_drive_prefix
+static inline int git_skip_dos_drive_prefix(const char **path)
+{
+	return 0;
+}
+#define skip_dos_drive_prefix git_skip_dos_drive_prefix
+#endif
+
 #ifndef is_dir_sep
 static inline int git_is_dir_sep(int c)
 {
path.c +5 -9
index 3cd155e..8b7e168 100644
@@ -782,13 +782,10 @@ const char *relative_path(const char *in, const char *prefix,
 	else if (!prefix_len)
 		return in;
 
-	if (have_same_root(in, prefix)) {
+	if (have_same_root(in, prefix))
 		/* bypass dos_drive, for "c:" is identical to "C:" */
-		if (has_dos_drive_prefix(in)) {
-			i = 2;
-			j = 2;
-		}
-	} else {
+		i = j = has_dos_drive_prefix(in);
+	else {
 		return in;
 	}
 
@@ -943,11 +940,10 @@ const char *remove_leading_path(const char *in, const char *prefix)
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
+	int i;
 
-	if (has_dos_drive_prefix(src)) {
+	for (i = has_dos_drive_prefix(src); i > 0; i--)
 		*dst++ = *src++;
-		*dst++ = *src++;
-	}
 	dst0 = dst;
 
 	if (is_dir_sep(*src)) {
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#7 lore raw

Re: [PATCH v2 1/4] Refactor skipping DOS drive prefixes

Quoted 10 lines · Johannes Schindelin

Junio Hamano pointed out that there is an implicit assumption in pretty much all the code calling has_dos_drive_prefix(): it assumes that the DOS drive prefix is always two bytes long.

While this assumption is pretty safe, we can still make the code more readable and less error-prone by introducing a function that skips the DOS drive prefix safely.

While at it, we change the has_dos_drive_prefix() return value: it now returns the number of bytes to be skipped if there is a DOS drive prefix.

With this change, code such as:

    for (i = has_dos_drive_prefix(src); i > 0; i--)
        ...

in path.c reads a bit oddly. Renaming the function might help. For instance:

    for (i = dos_drive_prefix_len(src); i > 0; i--)
        ...
Quoted 1 line
#8 lore raw

Re: [PATCH v2 1/4] Refactor skipping DOS drive prefixes

Quoted 9 lines · Eric Sunshine

With this change, code such as:

    for (i = has_dos_drive_prefix(src); i > 0; i--)
        ...

in path.c reads a bit oddly. Renaming the function might help. For instance:

    for (i = dos_drive_prefix_len(src); i > 0; i--)
        ...

Renaming may be unnecessary churn, but I do not think we mind an additional synonym, e.g.

    #define has_dos_drive_prefix(x) dos_drive_prefix_len(x)

if some people prefer.

#9 lore raw

Re: [PATCH v2 1/4] Refactor skipping DOS drive prefixes

Hi Eric & Junio,

Quoted 12 lines · Junio C Hamano
Quoted 9 lines · Eric Sunshine

With this change, code such as:

    for (i = has_dos_drive_prefix(src); i > 0; i--)
        ...

in path.c reads a bit oddly. Renaming the function might help. For instance:

    for (i = dos_drive_prefix_len(src); i > 0; i--)
        ...
Quoted 6 lines

Renaming may be unnecessary churn, but I do not think we mind an additional synonym, e.g.

    #define has_dos_drive_prefix(x) dos_drive_prefix_len(x)

if some people prefer.

I am actually not so sure about this: if I read dos_drive_prefix_len(path) I would have assumed the return value to be -1 if path does not, in fact, have a DOS drive prefix.

Sure, returning the length of the DOS drive prefix when just asking whether it has one is a bit surprising at first, but it also makes sense:
we already have that information, so we might just as well use it.

In any case, I think this change (if it is really considered desirable) could easily be an add-on patch by people who care about this ;-)

Ciao,
Dscho

#10 lore raw

Re: [PATCH v2 1/4] Refactor skipping DOS drive prefixes

Quoted 2 lines · Johannes Schindelin

In any case, I think this change (if it is really considered desirable) could easily be an add-on patch by people who care about this ;-)

Yup, in case if it was unclear, that is what I meant.

#11 lore raw

PATCH v2 2/4 compat/basename: make basename() conform to POSIX

In reply to Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("abc") should both return ".".

---
 compat/basename.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
compat/basename.c +17 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
+				*path = '\0';
 	}
 	return (char *)base;
 }
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#12 lore raw

Re: [PATCH v2 2/4] compat/basename: make basename() conform to POSIX

Quoted 3 lines · Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("abc") should both return ".".

Did you mean basename("abc"), not basename(""), here?

Quoted 37 lines
---
 compat/basename.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
compat/basename.c +17 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
+				*path = '\0';
 	}
 	return (char *)base;
 }
#13 lore raw

Re: [PATCH v2 2/4] compat/basename: make basename() conform to POSIX

Hi Junio,

Quoted 7 lines · Junio C Hamano
Quoted 3 lines · Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("abc") should both return ".".

Did you mean basename("abc"), not basename(""), here?

I don't understand: I wrote basename("abc")... ;-)

Ciao,
Dscho

#14 lore raw

Re: [PATCH v2 2/4] compat/basename: make basename() conform to POSIX

Quoted 6 lines · Johannes Schindelin

Hi Junio,

Quoted 2 lines · Junio C Hamano

Johannes Schindelin <[email protected]> writes:

Quoted 7 lines
Quoted 5 lines
Quoted 3 lines

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("abc") should both return ".".

Did you mean basename("abc"), not basename(""), here?

I don't understand: I wrote basename("abc")... ;-)

Yeah, I read that. What I didn't read was you wrote 'path' between slashes in the first example (the MUA was trying to render it as "path" in italic or something silly like that), and made me
confused: If 'path' has to go to 'path', what's so special about 'abc' to make it go '.'?

Upon second reading I noticed that slashes around the first one, but forgot to remove the "why 'abc'" comment.

#15 lore raw

PATCH v2 3/4 Provide a dirname() function when NO_LIBGEN_H=YesPlease

In reply to Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---
 compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 46 insertions(+)
compat/basename.c +44 -0
index 0f1b0b0..0a2ed25 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../strbuf.h"
 
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash = NULL, c;
+	int dos_drive_prefix;
+
+	if (!p)
+		return ".";
+
+	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
+		static struct strbuf buf = STRBUF_INIT;
+
+dot:
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+		return buf.buf;
+	}
+
+	/*
+	 * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+	 * should return "//", but dirname("///") should return "/" again.
+	 */
+	if (is_dir_sep(*p)) {
+		if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+			return path;
+		slash = ++p;
+	}
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+
+	if (!slash)
+		goto dot;
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +2 -0
index 0d66f3a..94f311a 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#16 lore raw
Quoted 36 lines · Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---
 compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 46 insertions(+)
compat/basename.c +6 -0
index 0f1b0b0..0a2ed25 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../strbuf.h"
 
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash = NULL, c;
+	int dos_drive_prefix;

compat/basename.c char *gitbasename (char *path) · line 43

Quoted 10 lines
compat/basename.c +10 -0
+
+	if (!p)
+		return ".";
+
+	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
+		static struct strbuf buf = STRBUF_INIT;
+
+dot:
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);

OK, so "Z:" becomes "Z:." (I missed the final '.' in my first reading), which sounds sensible.

Quoted 12 lines
compat/basename.c +12 -0
+		return buf.buf;
+	}
+
+	/*
+	 * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+	 * should return "//", but dirname("///") should return "/" again.
+	 */
+	if (is_dir_sep(*p)) {
+		if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+			return path;
+		slash = ++p;
+	}

compat/basename.c char *gitbasename (char *path) · line 65

Quoted 10 lines
compat/basename.c +10 -0
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}

OK, so we find the first slash in a run of slashes, unless that run of slashes is at the very end of the path. Either slash is left NUL (when there is no dir-sep) in which case we want to say "current directory", or that slash is at the end of the directory name we want to return in which case we just stuff NUL and return the path.

Sounds sensible.

compat/basename.c char *gitbasename (char *path) · line 68

Quoted 3 lines
compat/basename.c +3 -0
+
+	if (!slash)
+		goto dot;

;-) this is tricky. I wondered what the value of dos_drive_prefix at this point in my first reading, but this is correct. We must have done the skip_dos_drive_prefix() thing when we came into the function already, so it is either 2 (when the original had Z: in front) or 0 (for all other cases).

OK.

Quoted 16 lines
compat/basename.c +3 -0
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +2 -0
index 0d66f3a..94f311a 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
#17 lore raw

PATCH v2 4/4 t0060: verify that basename() and dirname() work as expected

In reply to Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 171 insertions(+)
t/t0060-path-utils.sh +3 -0
index 627ef85..f0152a7 100755
@@ -59,6 +59,9 @@ case $(uname -s) in
 	;;
 esac
 
+test_expect_success basename 'test-path-utils basename'
+test_expect_success dirname 'test-path-utils dirname'
+
 norm_path "" ""
 norm_path . ""
 norm_path ./ ""
test-path-utils.c +168 -0

180 lines not on this page · raw message

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#18 lore raw

Re: [PATCH v2 4/4] t0060: verify that basename() and dirname() work as expected

Quoted 12 lines · Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 41

Quoted 3 lines
test-path-utils.c +3 -0
@@ -39,6 +39,168 @@ static void normalize_argv_string(const char **var, const char *input)
+struct test_data {
+       char *from;  /* input:  transform from this ... */
+       char *to;    /* output: ... to this.            */

Can these be 'const'? If I'm reading the code correctly, I don't think these values ever get passed directly to functions expecting non-const strings.

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 51

Quoted 7 lines
test-path-utils.c +7 -0
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+       const char *funcname)
+{
+       int failed = 0, i;
+       static char buffer[1024];

Why is this 'static'? It is never accessed outside of this scope.

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 64

Quoted 13 lines
test-path-utils.c +13 -0
+       char *to;
+
+       for (i = 0; data[i].to; i++) {
+               if (!data[i].from)
+                       to = func(NULL);
+               else {
+                       strcpy(buffer, data[i].from);
+                       to = func(buffer);
+               }
+               if (strcmp(to, data[i].to)) {
+                       error("FAIL: %s(%s) => '%s' != '%s'\n",
+                               funcname, data[i].from, to, data[i].to);
+                       failed++;

Since 'failed' is only ever used as a boolean, it might be clearer to say:

    failed = 1;

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 67

Quoted 3 lines
test-path-utils.c +3 -0
+               }
+       }
+       return !!failed;

And then simply:

    return failed;

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 72

Quoted 5 lines
test-path-utils.c +5 -0
+}
+
+static struct test_data basename_data[] = {
+       /* --- POSIX type paths --- */
+       { NULL,              "."    },

NULL is tested here.

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 135

Quoted 5 lines
test-path-utils.c +4 -0
+       { "",                "."    },
+       { ".",               "."    },
[...]
+#endif
+       { NULL,              "."    },

And also here. Is that intentional?

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 200

Quoted 9 lines
test-path-utils.c +8 -0
+       { NULL,              NULL   }
+};
+
+static struct test_data dirname_data[] = {
+       /* --- POSIX type paths --- */
+       { NULL,              "."      },
[...]
+#endif
+       { NULL,              "."      },

Ditto.

Quoted 2 lines
test-path-utils.c +2 -0
+       { NULL,              NULL     }
+};
#19 lore raw

Re: [PATCH v2 4/4] t0060: verify that basename() and dirname() work as expected

Hi Eric,

Quoted 9 lines · Eric Sunshine
Quoted 7 lines · Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

Quoted 12 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 41

Quoted 8 lines

---

test-path-utils.c +3 -0
@@ -39,6 +39,168 @@ static void normalize_argv_string(const char **var, const char *input)
+struct test_data {
+       char *from;  /* input:  transform from this ... */
+       char *to;    /* output: ... to this.            */

Can these be 'const'? If I'm reading the code correctly, I don't think these values ever get passed directly to functions expecting non-const strings.

This, and ...

Quoted 9 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 51

Quoted 7 lines
test-path-utils.c +7 -0
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+       const char *funcname)
+{
+       int failed = 0, i;
+       static char buffer[1024];

Why is this 'static'? It is never accessed outside of this scope.

... this, and ...

Quoted 14 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 64

Quoted 13 lines
test-path-utils.c +13 -0
+       char *to;
+
+       for (i = 0; data[i].to; i++) {
+               if (!data[i].from)
+                       to = func(NULL);
+               else {
+                       strcpy(buffer, data[i].from);
+                       to = func(buffer);
+               }
+               if (strcmp(to, data[i].to)) {
+                       error("FAIL: %s(%s) => '%s' != '%s'\n",
+                               funcname, data[i].from, to, data[i].to);
+                       failed++;
Quoted 3 lines

Since 'failed' is only ever used as a boolean, it might be clearer to say:

    failed = 1;

... this and ...

Quoted 16 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 67

Quoted 3 lines
test-path-utils.c +3 -0
+               }
+       }
+       return !!failed;

And then simply:

    return failed;

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 72

Quoted 5 lines
test-path-utils.c +5 -0
+}
+
+static struct test_data basename_data[] = {
+       /* --- POSIX type paths --- */
+       { NULL,              "."    },

NULL is tested here.

Quoted 7 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 135

Quoted 5 lines
test-path-utils.c +4 -0
+       { "",                "."    },
+       { ".",               "."    },
[...]
+#endif
+       { NULL,              "."    },

And also here. Is that intentional?

... this are all valid concerns that I now addressed locally, so they will be fixed in the next iteration.

Thanks,
Dscho

#20 lore raw

Re: [PATCH v2 4/4] t0060: verify that basename() and dirname() work as expected

In reply to Johannes Schindelin
Quoted 215 lines · Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 171 insertions(+)
t/t0060-path-utils.sh +3 -0
index 627ef85..f0152a7 100755
@@ -59,6 +59,9 @@ case $(uname -s) in
        ;;
 esac
 
+test_expect_success basename 'test-path-utils basename'
+test_expect_success dirname 'test-path-utils dirname'
+
 norm_path "" ""
 norm_path . ""
 norm_path ./ ""
test-path-utils.c +168 -1
index c67bf65..74e74c9 100644
@@ -39,6 +39,168 @@ static void normalize_argv_string(const char **var, const char *input)
                die("Bad value: %s\n", input);
 }
 
+struct test_data {
+       char *from;  /* input:  transform from this ... */
+       char *to;    /* output: ... to this.            */
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+       const char *funcname)
+{
+       int failed = 0, i;
+       static char buffer[1024];
+       char *to;
+
+       for (i = 0; data[i].to; i++) {
+               if (!data[i].from)
+                       to = func(NULL);
+               else {
+                       strcpy(buffer, data[i].from);
+                       to = func(buffer);
+               }
+               if (strcmp(to, data[i].to)) {
+                       error("FAIL: %s(%s) => '%s' != '%s'\n",
+                               funcname, data[i].from, to, data[i].to);
+                       failed++;
+               }
+       }
+       return !!failed;
+}
+
+static struct test_data basename_data[] = {
+       /* --- POSIX type paths --- */
+       { NULL,              "."    },
+       { "",                "."    },
+       { ".",               "."    },
+       { "..",              ".."   },
+       { "/",               "/"    },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
+       { "//",              "//"   },
+       { "///",             "//"   },
+       { "////",            "//"   },
+#else
+       { "//",              "/"    },
+       { "///",             "/"    },
+       { "////",            "/"    },
+#endif
+       { "usr",             "usr"  },
+       { "/usr",            "usr"  },
+       { "/usr/",           "usr"  },
+       { "/usr//",          "usr"  },
+       { "/usr/lib",        "lib"  },
+       { "usr/lib",         "lib"  },
+       { "usr/lib///",      "lib"  },
+
+#if defined(__MINGW32__) || defined(_MSC_VER)
+
+       /* --- win32 type paths --- */
+       { "\\usr",           "usr"  },
+       { "\\usr\\",         "usr"  },
+       { "\\usr\\\\",       "usr"  },
+       { "\\usr\\lib",      "lib"  },
+       { "usr\\lib",        "lib"  },
+       { "usr\\lib\\\\\\",  "lib"  },
+       { "C:/usr",          "usr"  },
+       { "C:/usr",          "usr"  },
+       { "C:/usr/",         "usr"  },
+       { "C:/usr//",        "usr"  },
+       { "C:/usr/lib",      "lib"  },
+       { "C:usr/lib",       "lib"  },
+       { "C:usr/lib///",    "lib"  },
+       { "C:",              "."    },
+       { "C:a",             "a"    },
+       { "C:/",             "/"    },
+       { "C:///",           "/"    },
+#if defined(NO_LIBGEN_H)
+       { "\\",              "\\"   },
+       { "\\\\",            "\\"   },
+       { "\\\\\\",          "\\"   },
+#else
+
+       /* win32 platform variations: */
+#if defined(__MINGW32__)
+       { "\\",              "/"    },
+       { "\\\\",            "/"    },
+       { "\\\\\\",          "/"    },
+#endif
+
+#if defined(_MSC_VER)
+       { "\\",              "\\"   },
+       { "\\\\",            "\\"   },
+       { "\\\\\\",          "\\"   },
+#endif
+
+#endif
+#endif
+       { NULL,              "."    },
+       { NULL,              NULL   }
+};
+
+static struct test_data dirname_data[] = {
+       /* --- POSIX type paths --- */
+       { NULL,              "."      },
+       { "",                "."      },
+       { ".",               "."      },
+       { "..",              "."      },
+       { "/",               "/"      },
+       { "//",              "//"     },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
+       { "///",             "//"     },
+       { "////",            "//"     },
+#else
+       { "///",             "/"      },
+       { "////",            "/"      },
+#endif
+       { "usr",             "."      },
+       { "/usr",            "/"      },
+       { "/usr/",           "/"      },
+       { "/usr//",          "/"      },
+       { "/usr/lib",        "/usr"   },
+       { "usr/lib",         "usr"    },
+       { "usr/lib///",      "usr"    },
+
+#if defined(__MINGW32__) || defined(_MSC_VER)
+
+       /* --- win32 type paths --- */
+       { "\\",              "\\"     },
+       { "\\\\",            "\\\\"   },
+       { "\\usr",           "\\"     },
+       { "\\usr\\",         "\\"     },
+       { "\\usr\\\\",       "\\"     },
+       { "\\usr\\lib",      "\\usr"  },
+       { "usr\\lib",        "usr"    },
+       { "usr\\lib\\\\\\",  "usr"    },
+       { "C:a",             "C:."    },
+       { "C:/",             "C:/"    },
+       { "C:///",           "C:/"    },
+       { "C:/usr",          "C:/"    },
+       { "C:/usr/",         "C:/"    },
+       { "C:/usr//",        "C:/"    },
+       { "C:/usr/lib",      "C:/usr" },
+       { "C:usr/lib",       "C:usr"  },
+       { "C:usr/lib///",    "C:usr"  },
+       { "\\\\\\",          "\\"     },
+       { "\\\\\\\\",        "\\"     },
+#if defined(NO_LIBGEN_H)
+       { "C:",              "C:."    },
+#else
+
+       /* win32 platform variations: */
+#if defined(__MINGW32__)
+       /* the following is clearly wrong ... */
+       { "C:",              "."      },
+#endif
+
+#if defined(_MSC_VER)
+       { "C:",              "C:."    },
+#endif
+
+#endif
+#endif
+       { NULL,              "."      },
+       { NULL,              NULL     }
+};
+
 int main(int argc, char **argv)
 {
        if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) {
@@ -133,6 +295,12 @@ int main(int argc, char **argv)
                return 0;
        }
 
+       if (argc == 2 && !strcmp(argv[1], "basename"))
+               return test_function(basename_data, basename, argv[1]);
+
+       if (argc == 2 && !strcmp(argv[1], "dirname"))
+               return test_function(dirname_data, dirname, argv[1]);
+
        fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
                argv[1] ? argv[1] : "(there was none)");
        return 1;
--
Quoted 5 lines

2.6.3.windows.1.300.g1c25e49
--
To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

Test fails on my Mac:

expecting success: test-path-utils dirname
error: FAIL: dirname(//) => '/' != '//'

not ok 2 - dirname
# test-path-utils dirname

#21 lore raw

Re: [PATCH v2 4/4] t0060: verify that basename() and dirname() work as expected

Quoted 7 lines · Michael Blume

Test fails on my Mac:

expecting success: test-path-utils dirname
error: FAIL: dirname(//) => '/' != '//'

not ok 2 - dirname
# test-path-utils dirname

Thanks for reporting.

Ramsay gave a nice analysis at

http://thread.gmane.org/gmane.comp.version-control.git/283928

and Dscho already is working on it, IIUC.

Thanks.

#22 lore raw

Re: [PATCH v2 4/4] t0060: verify that basename() and dirname() work as expected

Hi,

[thanks, Junio, for culling the quoted text to the essential part]

Quoted 10 lines · Junio C Hamano
Quoted 7 lines · Michael Blume

Test fails on my Mac:

expecting success: test-path-utils dirname
error: FAIL: dirname(//) => '/' != '//'

not ok 2 - dirname
# test-path-utils dirname

Quoted 7 lines

Thanks for reporting.

Ramsay gave a nice analysis at

http://thread.gmane.org/gmane.comp.version-control.git/283928

and Dscho already is working on it, IIUC.

I already provided a hot fix:
http://article.gmane.org/gmane.comp.version-control.git/283893

And yes, I also work on a proper fix.

Ciao,
Dscho

#23 lore raw

v3 cover Ensure that we can build without libgen.h

In reply to Johannes Schindelin

This mini series adds a fall-back for the dirname() function that we use e.g. in git-am. This is necessary because not all platforms have a working libgen.h.

While at it, we ensure that our basename() drop-in conforms to the POSIX specifications.

In addition to the interdiff vs v2, the commit message was fixed to mention basename("") as cornercase (not basename("abc")).

Johannes Schindelin (4):
Refactor skipping DOS drive prefixes compat/basename: make basename() conform to POSIX Provide a dirname() function when NO_LIBGEN_H=YesPlease
t0060: verify that basename() and dirname() work as expected

 compat/basename.c     |  66 ++++++++++++++++++--
 compat/mingw.c        |  14 ++---
 compat/mingw.h        |  10 ++-
 git-compat-util.h     |  10 +++
 path.c                |  14 ++---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 259 insertions(+), 24 deletions(-)

Interdiff vs v2:

diff --git a/test-path-utils.c b/test-path-utils.c
index 74e74c9..4ab68ac 100644
--- a/test-path-utils.c
+++ b/test-path-utils.c
@@ -40,15 +40,15 @@ static void normalize_argv_string(const char **var, const char *input)
}

struct test_data {
- char *from; /* input: transform from this ... */
- char *to; /* output: ... to this. */
+ const char *from; /* input: transform from this ... */
+ const char *to; /* output: ... to this. */
};

static int test_function(struct test_data *data, char *(*func)(char *input),
const char *funcname)
{
int failed = 0, i;
- static char buffer[1024];
+ char buffer[1024];
char *to;

for (i = 0; data[i].to; i++) {
@@ -61,10 +61,10 @@ static int test_function(struct test_data *data, char *(*func)(char *input),
if (strcmp(to, data[i].to)) {
error("FAIL: %s(%s) => '%s' != '%s'\n",
funcname, data[i].from, to, data[i].to);
- failed++;
+ failed = 1;
}
}
- return !!failed;
+ return failed;
}

static struct test_data basename_data[] = {
@@ -132,7 +132,6 @@ static struct test_data basename_data[] = {

#endif
#endif

 -	{ NULL,              "."    },
  	{ NULL,              NULL   }

};

@@ -197,7 +196,6 @@ static struct test_data dirname_data[] = {

#endif
#endif

 -	{ NULL,              "."      },
  	{ NULL,              NULL     }

};

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#24 lore raw

PATCH v3 1/4 Refactor skipping DOS drive prefixes

Junio Hamano pointed out that there is an implicit assumption in pretty much all the code calling has_dos_drive_prefix(): it assumes that the DOS drive prefix is always two bytes long.

While this assumption is pretty safe, we can still make the code more readable and less error-prone by introducing a function that skips the DOS drive prefix safely.

While at it, we change the has_dos_drive_prefix() return value: it now returns the number of bytes to be skipped if there is a DOS drive prefix.

---
 compat/basename.c |  4 +---
 compat/mingw.c    | 14 +++++---------
 compat/mingw.h    | 10 +++++++++-
 git-compat-util.h |  8 ++++++++
 path.c            | 14 +++++---------
 5 files changed, 28 insertions(+), 22 deletions(-)
compat/basename.c +1 -3
index d8f8a3c..9f00421 100644
@@ -4,9 +4,7 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	/* Skip over the disk name in MSDOS pathnames. */
-	if (has_dos_drive_prefix(path))
-		path += 2;
+	skip_dos_drive_prefix(&path);
 	for (base = path; *path; path++) {
 		if (is_dir_sep(*path))
 			base = path + 1;
compat/mingw.c +5 -9
index 5edea29..1b3530a 100644
@@ -1934,26 +1934,22 @@ pid_t waitpid(pid_t pid, int *status, int options)
 
 int mingw_offset_1st_component(const char *path)
 {
-	int offset = 0;
-	if (has_dos_drive_prefix(path))
-		offset = 2;
+	char *pos = (char *)path;
 
 	/* unc paths */
-	else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+	if (!skip_dos_drive_prefix(&pos) &&
+			is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 		/* skip server name */
-		char *pos = strpbrk(path + 2, "\\/");
+		pos = strpbrk(pos + 2, "\\/");
 		if (!pos)
 			return 0; /* Error: malformed unc path */
 
 		do {
 			pos++;
 		} while (*pos && !is_dir_sep(*pos));
-
-		offset = pos - path;
 	}
 
-	return offset + is_dir_sep(path[offset]);
+	return pos + is_dir_sep(*pos) - path;
 }
 
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
compat/mingw.h +9 -1
index 57ca477..b3e5044 100644
@@ -361,7 +361,15 @@ HANDLE winansi_get_osfhandle(int fd);
  * git specific compatibility
  */
 
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
+#define has_dos_drive_prefix(path) \
+	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+static inline int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
 {
git-compat-util.h +8 -0
index 2da0a75..0d66f3a 100644
@@ -335,6 +335,14 @@ static inline int git_has_dos_drive_prefix(const char *path)
 #define has_dos_drive_prefix git_has_dos_drive_prefix
 #endif
 
+#ifndef skip_dos_drive_prefix
+static inline int git_skip_dos_drive_prefix(const char **path)
+{
+	return 0;
+}
+#define skip_dos_drive_prefix git_skip_dos_drive_prefix
+#endif
+
 #ifndef is_dir_sep
 static inline int git_is_dir_sep(int c)
 {
path.c +5 -9
index 3cd155e..8b7e168 100644
@@ -782,13 +782,10 @@ const char *relative_path(const char *in, const char *prefix,
 	else if (!prefix_len)
 		return in;
 
-	if (have_same_root(in, prefix)) {
+	if (have_same_root(in, prefix))
 		/* bypass dos_drive, for "c:" is identical to "C:" */
-		if (has_dos_drive_prefix(in)) {
-			i = 2;
-			j = 2;
-		}
-	} else {
+		i = j = has_dos_drive_prefix(in);
+	else {
 		return in;
 	}
 
@@ -943,11 +940,10 @@ const char *remove_leading_path(const char *in, const char *prefix)
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
+	int i;
 
-	if (has_dos_drive_prefix(src)) {
+	for (i = has_dos_drive_prefix(src); i > 0; i--)
 		*dst++ = *src++;
-		*dst++ = *src++;
-	}
 	dst0 = dst;
 
 	if (is_dir_sep(*src)) {
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#25 lore raw

PATCH v3 2/4 compat/basename: make basename() conform to POSIX

In reply to Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("") should both return "." to conform.

---
 compat/basename.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
compat/basename.c +17 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
+				*path = '\0';
 	}
 	return (char *)base;
 }
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#26 lore raw

PATCH v3 3/4 Provide a dirname() function when NO_LIBGEN_H=YesPlease

In reply to Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---
 compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 46 insertions(+)
compat/basename.c +44 -0
index 0f1b0b0..0a2ed25 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../strbuf.h"
 
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	char *p = path, *slash = NULL, c;
+	int dos_drive_prefix;
+
+	if (!p)
+		return ".";
+
+	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
+		static struct strbuf buf = STRBUF_INIT;
+
+dot:
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+		return buf.buf;
+	}
+
+	/*
+	 * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+	 * should return "//", but dirname("///") should return "/" again.
+	 */
+	if (is_dir_sep(*p)) {
+		if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+			return path;
+		slash = ++p;
+	}
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+
+	if (!slash)
+		goto dot;
+	*slash = '\0';
+	return path;
+}
git-compat-util.h +2 -0
index 0d66f3a..94f311a 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#27 lore raw
Quoted 47 lines · Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---

compat/basename.c +32 -0
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
+char *gitdirname(char *path)
+{
+       char *p = path, *slash = NULL, c;
+       int dos_drive_prefix;
+
+       if (!p)
+               return ".";
+
+       if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
+               static struct strbuf buf = STRBUF_INIT;
+
+dot:
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+               return buf.buf;
+       }
+
+       /*
+        * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+        * should return "//", but dirname("///") should return "/" again.
+        */
+       if (is_dir_sep(*p)) {
+               if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+                       return path;
+               slash = ++p;
+       }
+       while ((c = *(p++)))
+               if (is_dir_sep(c)) {
+                       char *tentative = p - 1;
+
+                       /* POSIX.1-2001 says to ignore trailing slashes */
+                       while (is_dir_sep(*p))

compat/basename.c char *gitbasename (char *path) · line 67

Quoted 10 lines
compat/basename.c +10 -0
+                               p++;
+                       if (*p)
+                               slash = tentative;
+               }
+
+       if (!slash)
+               goto dot;
+       *slash = '\0';
+       return path;
+}

I wonder if this would be a bit easier to follow if it was structured something like this:

    static struct strbuf buf = STRBUF_INIT;

    if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
        goto dot;

    ...
    if (is_dir_sep(*p)) {
        ...
    }
    ...
    while ((c = *(p++)))
        ...

    if (slash) {
        *slash = '\0';
        return path;
    }

    dot:
    strbuf_reset(&buf);
    strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
    return buf.buf;
#28 lore raw
Quoted 17 lines · Eric Sunshine

I wonder if this would be a bit easier to follow if it was structured something like this:

    static struct strbuf buf = STRBUF_INIT;

    if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
        goto dot;

    ...
    if (is_dir_sep(*p)) {
        ...
    }
    ...
    while ((c = *(p++)))
        ...

    if (slash) {
Quoted 8 lines
        *slash = '\0';
        return path;
    }

    dot:
    strbuf_reset(&buf);
    strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
    return buf.buf;

I'll queue the one from Dscho as-is for today, but avoiding the "jump back to a place where it happens to have an identical clean-up that need to happen" and defining the clean-up path at the end like this would probably be easier to follow. It certainly would have saved one comment in the previous review cycle from me.

Thanks.

#29 lore raw

Hi Eric & Junio,

Quoted 28 lines · Junio C Hamano
Quoted 17 lines · Eric Sunshine

I wonder if this would be a bit easier to follow if it was structured something like this:

    static struct strbuf buf = STRBUF_INIT;

    if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
        goto dot;

    ...
    if (is_dir_sep(*p)) {
        ...
    }
    ...
    while ((c = *(p++)))
        ...

    if (slash) {
Quoted 8 lines
        *slash = '\0';
        return path;
    }

    dot:
    strbuf_reset(&buf);
    strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
    return buf.buf;
Quoted 5 lines

I'll queue the one from Dscho as-is for today, but avoiding the "jump back to a place where it happens to have an identical clean-up that need to happen" and defining the clean-up path at the end like this would probably be easier to follow. It certainly would have saved one comment in the previous review cycle from me.

Thanks! I changed it and will mail out v4 shortly.

Ciao,
Dscho

#30 lore raw

PATCH v3 4/4 t0060: verify that basename() and dirname() work as expected

In reply to Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 169 insertions(+)
t/t0060-path-utils.sh +3 -0
index 627ef85..f0152a7 100755
@@ -59,6 +59,9 @@ case $(uname -s) in
 	;;
 esac
 
+test_expect_success basename 'test-path-utils basename'
+test_expect_success dirname 'test-path-utils dirname'
+
 norm_path "" ""
 norm_path . ""
 norm_path ./ ""
test-path-utils.c +166 -0

178 lines not on this page · raw message

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#31 lore raw

v4 cover Ensure that we can build without libgen.h

In reply to Johannes Schindelin

This mini series adds a fall-back for the dirname() function that we use e.g. in git-am. This is necessary because not all platforms have a working libgen.h.

While at it, we ensure that our basename() drop-in conforms to the POSIX specifications.

In addition to Eric's style improvement, v4 also fixes the signature of skip_dos_drive_prefix() in the non-Windows case.

Johannes Schindelin (4):
Refactor skipping DOS drive prefixes compat/basename: make basename() conform to POSIX Provide a dirname() function when NO_LIBGEN_H=YesPlease
t0060: verify that basename() and dirname() work as expected

 compat/basename.c     |  66 ++++++++++++++++++--
 compat/mingw.c        |  14 ++---
 compat/mingw.h        |  10 ++-
 git-compat-util.h     |  10 +++
 path.c                |  14 ++---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 259 insertions(+), 24 deletions(-)

Interdiff vs v3:

diff --git a/compat/basename.c b/compat/basename.c index 0a2ed25..96bd953 100644
--- a/compat/basename.c
+++ b/compat/basename.c
@@ -29,20 +29,15 @@ char *gitbasename (char *path)

char *gitdirname(char *path)
{
+ static struct strbuf buf = STRBUF_INIT;
char *p = path, *slash = NULL, c;
int dos_drive_prefix;

if (!p)
return ".";

- if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p) {
- static struct strbuf buf = STRBUF_INIT;
-
-dot:
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
- return buf.buf;
- }
+ if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
+ goto dot;

/*
* POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
@@ -64,8 +59,13 @@ dot:
slash = tentative;
}

- if (!slash)
- goto dot;
- *slash = '\0';
- return path;
+ if (slash) {
+ *slash = '\0';
+ return path;
+ }
+
+dot:
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+ return buf.buf;
}
diff --git a/git-compat-util.h b/git-compat-util.h
index 94f311a..5f72f1c 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -338,7 +338,7 @@ static inline int git_has_dos_drive_prefix(const char *path)
#endif

#ifndef skip_dos_drive_prefix
-static inline int git_skip_dos_drive_prefix(const char **path) +static inline int git_skip_dos_drive_prefix(char **path)
{
return 0;
}

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#32 lore raw

PATCH v4 1/4 Refactor skipping DOS drive prefixes

Junio Hamano pointed out that there is an implicit assumption in pretty much all the code calling has_dos_drive_prefix(): it assumes that the DOS drive prefix is always two bytes long.

While this assumption is pretty safe, we can still make the code more readable and less error-prone by introducing a function that skips the DOS drive prefix safely.

While at it, we change the has_dos_drive_prefix() return value: it now returns the number of bytes to be skipped if there is a DOS drive prefix.

---
 compat/basename.c |  4 +---
 compat/mingw.c    | 14 +++++---------
 compat/mingw.h    | 10 +++++++++-
 git-compat-util.h |  8 ++++++++
 path.c            | 14 +++++---------
 5 files changed, 28 insertions(+), 22 deletions(-)
compat/basename.c +1 -3
index d8f8a3c..9f00421 100644
@@ -4,9 +4,7 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	/* Skip over the disk name in MSDOS pathnames. */
-	if (has_dos_drive_prefix(path))
-		path += 2;
+	skip_dos_drive_prefix(&path);
 	for (base = path; *path; path++) {
 		if (is_dir_sep(*path))
 			base = path + 1;
compat/mingw.c +5 -9
index 5edea29..1b3530a 100644
@@ -1934,26 +1934,22 @@ pid_t waitpid(pid_t pid, int *status, int options)
 
 int mingw_offset_1st_component(const char *path)
 {
-	int offset = 0;
-	if (has_dos_drive_prefix(path))
-		offset = 2;
+	char *pos = (char *)path;
 
 	/* unc paths */
-	else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+	if (!skip_dos_drive_prefix(&pos) &&
+			is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 		/* skip server name */
-		char *pos = strpbrk(path + 2, "\\/");
+		pos = strpbrk(pos + 2, "\\/");
 		if (!pos)
 			return 0; /* Error: malformed unc path */
 
 		do {
 			pos++;
 		} while (*pos && !is_dir_sep(*pos));
-
-		offset = pos - path;
 	}
 
-	return offset + is_dir_sep(path[offset]);
+	return pos + is_dir_sep(*pos) - path;
 }
 
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
compat/mingw.h +9 -1
index 57ca477..b3e5044 100644
@@ -361,7 +361,15 @@ HANDLE winansi_get_osfhandle(int fd);
  * git specific compatibility
  */
 
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
+#define has_dos_drive_prefix(path) \
+	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+static inline int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
 {
git-compat-util.h +8 -0
index 2da0a75..fbb11bb 100644
@@ -335,6 +335,14 @@ static inline int git_has_dos_drive_prefix(const char *path)
 #define has_dos_drive_prefix git_has_dos_drive_prefix
 #endif
 
+#ifndef skip_dos_drive_prefix
+static inline int git_skip_dos_drive_prefix(char **path)
+{
+	return 0;
+}
+#define skip_dos_drive_prefix git_skip_dos_drive_prefix
+#endif
+
 #ifndef is_dir_sep
 static inline int git_is_dir_sep(int c)
 {
path.c +5 -9
index 3cd155e..8b7e168 100644
@@ -782,13 +782,10 @@ const char *relative_path(const char *in, const char *prefix,
 	else if (!prefix_len)
 		return in;
 
-	if (have_same_root(in, prefix)) {
+	if (have_same_root(in, prefix))
 		/* bypass dos_drive, for "c:" is identical to "C:" */
-		if (has_dos_drive_prefix(in)) {
-			i = 2;
-			j = 2;
-		}
-	} else {
+		i = j = has_dos_drive_prefix(in);
+	else {
 		return in;
 	}
 
@@ -943,11 +940,10 @@ const char *remove_leading_path(const char *in, const char *prefix)
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
+	int i;
 
-	if (has_dos_drive_prefix(src)) {
+	for (i = has_dos_drive_prefix(src); i > 0; i--)
 		*dst++ = *src++;
-		*dst++ = *src++;
-	}
 	dst0 = dst;
 
 	if (is_dir_sep(*src)) {
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#33 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Am 12.01.2016 um 08:57 schrieb Johannes Schindelin:

Quoted 8 lines
compat/mingw.h +0 -0
index 57ca477..b3e5044 100644
@@ -361,7 +361,15 @@ HANDLE winansi_get_osfhandle(int fd);
   * git specific compatibility
   */
 

compat/mingw.h HANDLE winansi_get_osfhandle(int fd); · line 372

Quoted 10 lines
compat/mingw.h +9 -1
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
+#define has_dos_drive_prefix(path) \
+	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+static inline int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix

This triggers

CC alloc.o
In file included from git-compat-util.h:186,

                 from cache.h:4,
                 from alloc.c:12:

compat/mingw.h: In function 'mingw_skip_dos_drive_prefix':
compat/mingw.h:365: warning: implicit declaration of function 'isalpha'

when I build under the old MSYS environment. While I would understand that the old MSYS environment is end-of-lifed and not worth your time catering to, the error is still an indication of a problem.

Notice that mingw.h is #included in line 186 of git-compat-util.h, isalpha is only (re-)defined much later in line 790. That would explain the warning. What I do not understand is that you do not observe the same warning in your MSYS2/MINGWxx environment. It would mean that <ctype.h> is included somewhere.

At any rate, the resulting binary sometimes uses an isalpha implementation other than the one provided in git-compat-util.h. The result is most likely correct, but it is certainly not the intent, is it?

I did not attempt to build with MSVC, but it is not unlikely that it shows the same error.

I suggest to move the function definition out of line:

compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
 	char *pos = (char *)path;
compat/mingw.h +1 -6
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
 #define has_dos_drive_prefix(path) \
 	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
 #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
#34 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Quoted 28 lines · Johannes Sixt

I suggest to move the function definition out of line:

compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
 	char *pos = (char *)path;
compat/mingw.h +0 -0
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
 #define has_dos_drive_prefix(path) \
 	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)

compat/mingw.h HANDLE winansi_get_osfhandle(int fd); · line 366

Quoted 10 lines
compat/mingw.h +1 -6
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
 #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)

This sounds good to me. Dscho?

#35 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Hi Junio,

Quoted 28 lines · Junio C Hamano
Quoted 26 lines · Johannes Sixt

I suggest to move the function definition out of line:

compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
 	char *pos = (char *)path;
compat/mingw.h +0 -0
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
Quoted 14 lines

compat/mingw.h HANDLE winansi_get_osfhandle(int fd); · line 366

Quoted 12 lines
compat/mingw.h +1 -6
 #define has_dos_drive_prefix(path) \
 	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
 #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)

This sounds good to me. Dscho?

Yep, sounds good to me, too.

Personally, I have no inclination to add compatibility with the now-safely-obsolete MSys to my responsibilities, but if Hannes wants to do it, who am I to stand in his way? Especially when the fix is as trivial as here.

Ciao,
Dscho

#36 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Am 23.01.2016 um 09:25 schrieb Johannes Schindelin:

Quoted 47 lines

Hi Junio,

Quoted 28 lines · Junio C Hamano
Quoted 26 lines · Johannes Sixt

I suggest to move the function definition out of line:

compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int options)
  	return -1;
  }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
  int mingw_offset_1st_component(const char *path)
  {
  	char *pos = (char *)path;
compat/mingw.h +0 -0
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
Quoted 14 lines

compat/mingw.h HANDLE winansi_get_osfhandle(int fd); · line 366

Quoted 12 lines
compat/mingw.h +1 -6
  #define has_dos_drive_prefix(path) \
  	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
  #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
  #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
  static inline char *mingw_find_last_dir_sep(const char *path)

This sounds good to me. Dscho?

Quoted 6 lines

Yep, sounds good to me, too.

Personally, I have no inclination to add compatibility with the now-safely-obsolete MSys to my responsibilities, but if Hannes wants to do it, who am I to stand in his way? Especially when the fix is as trivial as here.

This is not a matter of compatibility. I am VERY curious why you do not see an error (or warning) without my proposed fixup. As I mentioned, isalpha() is defined much later than the definition of mingw_skip_dos_drive_prefix(). Where does your build get a declaration of isalpha() from?

-- Hannes

#37 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Hi Hannes,

Quoted 55 lines · Johannes Sixt

Am 23.01.2016 um 09:25 schrieb Johannes Schindelin:

Quoted 46 lines · Junio C Hamano
Quoted 30 lines
Quoted 27 lines · Johannes Sixt

I suggest to move the function definition out of line:

compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int
 options)
  	return -1;
  }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
  int mingw_offset_1st_component(const char *path)
  {
  	char *pos = (char *)path;
compat/mingw.h +0 -0
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
Quoted 14 lines

compat/mingw.h HANDLE winansi_get_osfhandle(int fd); · line 366

Quoted 12 lines
compat/mingw.h +1 -6
  #define has_dos_drive_prefix(path) \
  	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
  #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
  #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
  static inline char *mingw_find_last_dir_sep(const char *path)

This sounds good to me. Dscho?

Quoted 6 lines

Yep, sounds good to me, too.

Personally, I have no inclination to add compatibility with the now-safely-obsolete MSys to my responsibilities, but if Hannes wants to do it, who am I to stand in his way? Especially when the fix is as trivial as here.

Quoted 4 lines

This is not a matter of compatibility. I am VERY curious why you do not see an error (or warning) without my proposed fixup. As I mentioned, isalpha() is defined much later than the definition of mingw_skip_dos_drive_prefix(). Where does your build get a declaration of isalpha() from?

$ grep -w isalpha /mingw32/i686-w64-mingw32/include/*.h
/mingw32/i686-w64-mingw32/include/ctype.h:  _CRTIMP int __cdecl isalpha(int _C);
/mingw32/i686-w64-mingw32/include/ctype.h:#define __iscsymf(_c) (isalpha(_c) || ((_c)=='_'))

I guess that definition gets pulled in somehow.

Ciao,
Dscho

#38 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Am 24.01.2016 um 11:56 schrieb Johannes Schindelin:

Quoted 5 lines
$ grep -w isalpha /mingw32/i686-w64-mingw32/include/*.h
/mingw32/i686-w64-mingw32/include/ctype.h:  _CRTIMP int __cdecl isalpha(int _C);
/mingw32/i686-w64-mingw32/include/ctype.h:#define __iscsymf(_c) (isalpha(_c) || ((_c)=='_'))

I guess that definition gets pulled in somehow.

Ok, then, Junio, kindly replace js/dirname-basename~4 with this patch. (I hope I get the patch headers right for git-am.)

---- 8< ----
From: Johannes Schindelin <[email protected]>
Subject: [PATCH] Refactor skipping DOS drive prefixes

Junio noticed that there is an implicit assumption in pretty much all the code calling has_dos_drive_prefix(): it forces all of its callsites to hardcode the knowledge that the DOS drive prefix is always two bytes long.

While this assumption is pretty safe, we can still make the code more readable and less error-prone by introducing a function that skips the DOS drive prefix safely.

While at it, we change the has_dos_drive_prefix() return value: it now returns the number of bytes to be skipped if there is a DOS drive prefix.

[j6t: moved definition of mingw_skip_dos_drive_prefix() out of line]

---
 compat/basename.c |  4 +---
 compat/mingw.c    | 21 ++++++++++++---------
 compat/mingw.h    |  5 ++++-
 git-compat-util.h |  8 ++++++++
 path.c            | 14 +++++---------
 5 files changed, 30 insertions(+), 22 deletions(-)
compat/basename.c +1 -3
index d8f8a3c..9f00421 100644
@@ -4,9 +4,7 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	/* Skip over the disk name in MSDOS pathnames. */
-	if (has_dos_drive_prefix(path))
-		path += 2;
+	skip_dos_drive_prefix(&path);
 	for (base = path; *path; path++) {
 		if (is_dir_sep(*path))
 			base = path + 1;
compat/mingw.c +12 -9
index f74da23..0cebb61 100644
@@ -1915,28 +1915,31 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
-	int offset = 0;
-	if (has_dos_drive_prefix(path))
-		offset = 2;
+	char *pos = (char *)path;
 
 	/* unc paths */
-	else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) {
-
+	if (!skip_dos_drive_prefix(&pos) &&
+			is_dir_sep(pos[0]) && is_dir_sep(pos[1])) {
 		/* skip server name */
-		char *pos = strpbrk(path + 2, "\\/");
+		pos = strpbrk(pos + 2, "\\/");
 		if (!pos)
 			return 0; /* Error: malformed unc path */
 
 		do {
 			pos++;
 		} while (*pos && !is_dir_sep(*pos));
-
-		offset = pos - path;
 	}
 
-	return offset + is_dir_sep(path[offset]);
+	return pos + is_dir_sep(*pos) - path;
 }
 
 int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen)
compat/mingw.h +4 -1
index 738865c..2099b79 100644
@@ -358,7 +358,10 @@ HANDLE winansi_get_osfhandle(int fd);
  * git specific compatibility
  */
 
-#define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':')
+#define has_dos_drive_prefix(path) \
+	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
+int mingw_skip_dos_drive_prefix(char **path);
+#define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
 {
git-compat-util.h +8 -0
index 0feeae2..38397d7 100644
@@ -335,6 +335,14 @@ static inline int git_has_dos_drive_prefix(const char *path)
 #define has_dos_drive_prefix git_has_dos_drive_prefix
 #endif
 
+#ifndef skip_dos_drive_prefix
+static inline int git_skip_dos_drive_prefix(char **path)
+{
+	return 0;
+}
+#define skip_dos_drive_prefix git_skip_dos_drive_prefix
+#endif
+
 #ifndef is_dir_sep
 static inline int git_is_dir_sep(int c)
 {
path.c +5 -9
index 38f2ebd..747d6da 100644
@@ -544,13 +544,10 @@ const char *relative_path(const char *in, const char *prefix,
 	else if (!prefix_len)
 		return in;
 
-	if (have_same_root(in, prefix)) {
+	if (have_same_root(in, prefix))
 		/* bypass dos_drive, for "c:" is identical to "C:" */
-		if (has_dos_drive_prefix(in)) {
-			i = 2;
-			j = 2;
-		}
-	} else {
+		i = j = has_dos_drive_prefix(in);
+	else {
 		return in;
 	}
 
@@ -703,11 +700,10 @@ const char *remove_leading_path(const char *in, const char *prefix)
 int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
 {
 	char *dst0;
+	int i;
 
-	if (has_dos_drive_prefix(src)) {
+	for (i = has_dos_drive_prefix(src); i > 0; i--)
 		*dst++ = *src++;
-		*dst++ = *src++;
-	}
 	dst0 = dst;
 
 	if (is_dir_sep(*src)) {
Signature · 1 line
2.7.0.118.g90056ae
#39 lore raw

Re: [PATCH v4 1/4] Refactor skipping DOS drive prefixes

Quoted 9 lines · Johannes Sixt

Am 24.01.2016 um 11:56 schrieb Johannes Schindelin:

Quoted 5 lines
$ grep -w isalpha /mingw32/i686-w64-mingw32/include/*.h
/mingw32/i686-w64-mingw32/include/ctype.h:  _CRTIMP int __cdecl isalpha(int _C);
/mingw32/i686-w64-mingw32/include/ctype.h:#define __iscsymf(_c) (isalpha(_c) || ((_c)=='_'))

I guess that definition gets pulled in somehow.

Ok, then, Junio, kindly replace js/dirname-basename~4 with this patch. (I hope I get the patch headers right for git-am.)

Thanks for following it through.

If they are already in 'next', we'd want an incremental fixup, though.

    ... goes and looks ...

Yeah, unfortunately that is already in 'next'; could you make this into an incremental, which would come with its own log message that explains why the inlining was wrong?

Thanks.

#40 lore raw

PATCH 1/1 mingw: avoid linking to the C library's isalpha()

The implementation of mingw_skip_dos_drive_prefix() calls isalpha() via has_dos_drive_prefix(). Since the definition occurs long before isalpha() is defined in git-compat-util.h, my build environment reports:

CC alloc.o
In file included from git-compat-util.h:186,

                 from cache.h:4,
                 from alloc.c:12:

compat/mingw.h: In function 'mingw_skip_dos_drive_prefix':
compat/mingw.h:365: warning: implicit declaration of function 'isalpha'

Dscho does not see a similar warning in his build and suspects that ctype.h is included somehow behind the scenes. This implies that his build links to the C library's isalpha() and does not use git's isalpha().

To fix both the warning in my build and the inconsistency in Dscho's build, move the function definition to mingw.c. Then it picks up git's isalpha() because git-compat-util.h is included at the top of the file.

---
 compat/mingw.c | 7 +++++++
 compat/mingw.h | 7 +------
 2 files changed, 8 insertions(+), 6 deletions(-)
compat/mingw.c +7 -0
index 10a51c0..0cebb61 100644
@@ -1915,6 +1915,13 @@ pid_t waitpid(pid_t pid, int *status, int options)
 	return -1;
 }
 
+int mingw_skip_dos_drive_prefix(char **path)
+{
+	int ret = has_dos_drive_prefix(*path);
+	*path += ret;
+	return ret;
+}
+
 int mingw_offset_1st_component(const char *path)
 {
 	char *pos = (char *)path;
compat/mingw.h +1 -6
index 9b5db4e..2099b79 100644
@@ -360,12 +360,7 @@ HANDLE winansi_get_osfhandle(int fd);
 
 #define has_dos_drive_prefix(path) \
 	(isalpha(*(path)) && (path)[1] == ':' ? 2 : 0)
-static inline int mingw_skip_dos_drive_prefix(char **path)
-{
-	int ret = has_dos_drive_prefix(*path);
-	*path += ret;
-	return ret;
-}
+int mingw_skip_dos_drive_prefix(char **path);
 #define skip_dos_drive_prefix mingw_skip_dos_drive_prefix
 #define is_dir_sep(c) ((c) == '/' || (c) == '\\')
 static inline char *mingw_find_last_dir_sep(const char *path)
Signature · 1 line
2.7.0.118.g90056ae
#41 lore raw

Re: [PATCH] mingw: avoid linking to the C library's isalpha()

Thanks.

#42 lore raw

PATCH v4 2/4 compat/basename: make basename() conform to POSIX

In reply to Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("") should both return "." to conform.

---
 compat/basename.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
compat/basename.c +17 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
+				*path = '\0';
 	}
 	return (char *)base;
 }
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#43 lore raw

Re: [PATCH v4 2/4] compat/basename: make basename() conform to POSIX

Quoted 32 lines · Johannes Schindelin

According to POSIX, basename("/path/") should return "path", not "path/". Likewise, basename(NULL) and basename("") should both return "." to conform.

---
 compat/basename.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
compat/basename.c +10 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {

compat/basename.c line 27

Quoted 10 lines
compat/basename.c +7 -0
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
+				*path = '\0';
 	}
 	return (char *)base;
 }

I don't suppose it makes much difference, but I find my version slightly easier to read:

char *gitbasename (char *path)

{
	char *p;

	if (!path || !*path)
		return ".";
	/* skip drive designator, if any */
	if (has_dos_drive_prefix(path))
		path += 2;
	if (!*path)
		return ".";
	/* trim trailing directory separators */
	p = path + strlen(path) - 1;
	while (is_dir_sep(*p)) {
		if (p == path)
			return path;
		*p-- = '\0';
	}
	/* find begining of last path component */
	while (p > path && !is_dir_sep(*p))
		p--;
	if (is_dir_sep(*p))
		p++;
	return p;
}

ATB,
Ramsay Jones

#44 lore raw

Re: [PATCH v4 2/4] compat/basename: make basename() conform to POSIX

Hi Ramsay,

Quoted 29 lines · Ramsay Jones
Quoted 28 lines · Johannes Schindelin
compat/basename.c +16 -3
index 9f00421..0f1b0b0 100644
@@ -4,10 +4,24 @@
 char *gitbasename (char *path)
 {
 	const char *base;
-	skip_dos_drive_prefix(&path);
+
+	if (path)
+		skip_dos_drive_prefix(&path);
+
+	if (!path || !*path)
+		return ".";
+
 	for (base = path; *path; path++) {
-		if (is_dir_sep(*path))
-			base = path + 1;
+		if (!is_dir_sep(*path))
+			continue;
+		do {
+			path++;
+		} while (is_dir_sep(*path));
+		if (*path)
+			base = path;
+		else
+			while (--path != base && is_dir_sep(*path))
Quoted 8 lines

compat/basename.c line 27

Quoted 5 lines
compat/basename.c +1 -0
+				*path = '\0';
 	}
 	return (char *)base;
 }

I don't suppose it makes much difference, but I find my version slightly easier to read:

Yours is better documented, yes, but as I said, I started from what Git already had and tried to provide as minimal changes as possible, to make reviewing easy. In any case, I am very reluctant when it comes to wholesale code replacements: in my experience, these frequently lead to new, entertaining and unintended behavior. I worked with somebody who (for the sake of charity) in the following I will reference only by his most frequent commit message: Dr "Completely new version" (and yes, this was the extent of the commit message). If you buy me a beer or three, I will gladly tell you all the fun I had trying to find the regressions in that code.

In short: please accept that my decision to build on the existing code rather than replacing it had nothing to do with your code.

Ciao,
Dscho

#45 lore raw

PATCH v4 3/4 Provide a dirname() function when NO_LIBGEN_H=YesPlease

In reply to Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---
 compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 46 insertions(+)
compat/basename.c +44 -0
index 0f1b0b0..96bd953 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../strbuf.h"
 
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	static struct strbuf buf = STRBUF_INIT;
+	char *p = path, *slash = NULL, c;
+	int dos_drive_prefix;
+
+	if (!p)
+		return ".";
+
+	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
+		goto dot;
+
+	/*
+	 * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+	 * should return "//", but dirname("///") should return "/" again.
+	 */
+	if (is_dir_sep(*p)) {
+		if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+			return path;
+		slash = ++p;
+	}
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+
+	if (slash) {
+		*slash = '\0';
+		return path;
+	}
+
+dot:
+	strbuf_reset(&buf);
+	strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+	return buf.buf;
+}
git-compat-util.h +2 -0
index fbb11bb..5f72f1c 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV
Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#46 lore raw
Quoted 64 lines · Johannes Schindelin

When there is no libgen.h to our disposal, we miss the dirname() function.

So far, we only had one user of that function: credential-cache--daemon (which was only compiled when Unix sockets are available, anyway). But now we also have builtin/am.c as user, so we need it.

Since dirname() is a sibling of basename(), we simply put our very own gitdirname() implementation next to gitbasename() and use it if NO_LIBGEN_H has been set.

---
 compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
 git-compat-util.h |  2 ++
 2 files changed, 46 insertions(+)
compat/basename.c +34 -0
index 0f1b0b0..96bd953 100644
@@ -1,4 +1,5 @@
 #include "../git-compat-util.h"
+#include "../strbuf.h"
 
 /* Adapted from libiberty's basename.c.  */
 char *gitbasename (char *path)
@@ -25,3 +26,46 @@ char *gitbasename (char *path)
 	}
 	return (char *)base;
 }
+
+char *gitdirname(char *path)
+{
+	static struct strbuf buf = STRBUF_INIT;
+	char *p = path, *slash = NULL, c;
+	int dos_drive_prefix;
+
+	if (!p)
+		return ".";
+
+	if ((dos_drive_prefix = skip_dos_drive_prefix(&p)) && !*p)
+		goto dot;
+
+	/*
+	 * POSIX.1-2001 says dirname("/") should return "/", and dirname("//")
+	 * should return "//", but dirname("///") should return "/" again.
+	 */
+	if (is_dir_sep(*p)) {
+		if (!p[1] || (is_dir_sep(p[1]) && !p[2]))
+			return path;
+		slash = ++p;
+	}
+	while ((c = *(p++)))
+		if (is_dir_sep(c)) {
+			char *tentative = p - 1;
+
+			/* POSIX.1-2001 says to ignore trailing slashes */
+			while (is_dir_sep(*p))
+				p++;
+			if (*p)
+				slash = tentative;
+		}
+

compat/basename.c char *gitbasename (char *path) · line 71

Quoted 10 lines
compat/basename.c +10 -0
+	if (slash) {
+		*slash = '\0';
+		return path;
+	}
+
+dot:
+	strbuf_reset(&buf);
+	strbuf_addf(&buf, "%.*s.", dos_drive_prefix, path);
+	return buf.buf;
+}

Again, I find my version much easier to read:

char *gitdirname(char *path)

{
	char *p, *start;

	if (!path || !*path)
		return ".";
	start = path;
	/* skip drive designator, if any */
	if (has_dos_drive_prefix(path))
		start += 2;
	/* check for // */
	if (strcmp(start, "//") == 0)
		return path;
	/* check for \\ */
	if (is_dir_sep('\\') && strcmp(start, "\\\\") == 0)
		return path;
	/* trim trailing directory separators */
	p = path + strlen(path) - 1;
	while (is_dir_sep(*p)) {
		if (p == start)
			return path;
		*p-- = '\0';
	}
	/* find begining of last path component */
	while (p > start && !is_dir_sep(*p))
		p--;
	/* terminate dirname */
	if (p == start && !is_dir_sep(*p))
		*p++ = '.';
	else if (p == start)
		p++;
	*p = '\0';
	return path;
}

git-compat-util.h struct itimerval { · line 253

Quoted 6 lines
git-compat-util.h +0 -0
index fbb11bb..5f72f1c 100644
@@ -253,6 +253,8 @@ struct itimerval {
 #else

Also, when compiling on Cygwin with NO_LIBGEN_H, I need to include the following here:

#undef basename

in order to suppress approx 230 warnings about the redefinition of the basename macro.

(I suppose that should go in the previous commit. dunno)

git-compat-util.h struct itimerval { · line 260

Quoted 8 lines
git-compat-util.h +2 -0
 #define basename gitbasename
 extern char *gitbasename(char *);
+#define dirname gitdirname
+extern char *gitdirname(char *);
 #endif
 
 #ifndef NO_ICONV

ATB,
Ramsay Jones

#47 lore raw

Hi Ramsay,

Quoted 9 lines · Ramsay Jones

Also, when compiling on Cygwin with NO_LIBGEN_H, I need to include the following here:

#undef basename

in order to suppress approx 230 warnings about the redefinition of the basename macro.

(I suppose that should go in the previous commit. dunno)

I think this is an incorrect use of NO_LIBGEN_H (because Cygwin obviously has it), but in any case, it is a completely independent issue from fixing/testing basename()/dirname(), so your #undef basename should be in a completely separate commit, methinks.

Ciao,
Dscho

#48 lore raw
Quoted 12 lines · Johannes Schindelin

Hi Ramsay,

Quoted 8 lines · Ramsay Jones

Also, when compiling on Cygwin with NO_LIBGEN_H, I need to include the following here:

#undef basename

in order to suppress approx 230 warnings about the redefinition of the basename macro.

Quoted 6 lines
Quoted 1 line

(I suppose that should go in the previous commit. dunno)

I think this is an incorrect use of NO_LIBGEN_H (because Cygwin obviously has it), but in any case, it is a completely independent issue from fixing/testing basename()/dirname(), so your #undef basename should be in a completely separate commit, methinks.

OK. I think this worked fine on 32-bit cygwin, but the system headers have changed quite a bit on 64-bit cygwin and I only tried it for the first time yesterday. (It was helpful in the debugging process at one point to be able to build with NO_LIBGEN_H on all platforms ...)

ATB,
Ramsay Jones

#49 lore raw

PATCH v4 4/4 t0060: verify that basename() and dirname() work as expected

In reply to Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 169 insertions(+)
t/t0060-path-utils.sh +3 -0
index 627ef85..f0152a7 100755
@@ -59,6 +59,9 @@ case $(uname -s) in
 	;;
 esac
 
+test_expect_success basename 'test-path-utils basename'
+test_expect_success dirname 'test-path-utils dirname'
+
 norm_path "" ""
 norm_path . ""
 norm_path ./ ""
test-path-utils.c +166 -0

178 lines not on this page · raw message

Signature · 1 line
2.6.3.windows.1.300.g1c25e49
#50 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Quoted 89 lines · Johannes Schindelin

Unfortunately, some libgen implementations yield outcomes different from what Git expects. For example, mingw-w64-crt provides a basename() function, that shortens path0/ to path!

So let's verify that the basename() and dirname() functions we use conform to what Git expects.

---
 t/t0060-path-utils.sh |   3 +
 test-path-utils.c     | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 169 insertions(+)
t/t0060-path-utils.sh +3 -0
index 627ef85..f0152a7 100755
@@ -59,6 +59,9 @@ case $(uname -s) in
 	;;
 esac
 
+test_expect_success basename 'test-path-utils basename'
+test_expect_success dirname 'test-path-utils dirname'
+
 norm_path "" ""
 norm_path . ""
 norm_path ./ ""
test-path-utils.c +53 -0
index c67bf65..4ab68ac 100644
@@ -39,6 +39,166 @@ static void normalize_argv_string(const char **var, const char *input)
 		die("Bad value: %s\n", input);
 }
 
+struct test_data {
+	const char *from;  /* input:  transform from this ... */
+	const char *to;    /* output: ... to this.            */
+};
+
+static int test_function(struct test_data *data, char *(*func)(char *input),
+	const char *funcname)
+{
+	int failed = 0, i;
+	char buffer[1024];
+	char *to;
+
+	for (i = 0; data[i].to; i++) {
+		if (!data[i].from)
+			to = func(NULL);
+		else {
+			strcpy(buffer, data[i].from);
+			to = func(buffer);
+		}
+		if (strcmp(to, data[i].to)) {
+			error("FAIL: %s(%s) => '%s' != '%s'\n",
+				funcname, data[i].from, to, data[i].to);
+			failed = 1;
+		}
+	}
+	return failed;
+}
+
+static struct test_data basename_data[] = {
+	/* --- POSIX type paths --- */
+	{ NULL,              "."    },
+	{ "",                "."    },
+	{ ".",               "."    },
+	{ "..",              ".."   },
+	{ "/",               "/"    },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
+	{ "//",              "//"   },
+	{ "///",             "//"   },
+	{ "////",            "//"   },
+#else
+	{ "//",              "/"    },
+	{ "///",             "/"    },
+	{ "////",            "/"    },
+#endif
+	{ "usr",             "usr"  },
+	{ "/usr",            "usr"  },
+	{ "/usr/",           "usr"  },
+	{ "/usr//",          "usr"  },
+	{ "/usr/lib",        "lib"  },
+	{ "usr/lib",         "lib"  },
+	{ "usr/lib///",      "lib"  },
+
+#if defined(__MINGW32__) || defined(_MSC_VER)

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 104

Quoted 10 lines
test-path-utils.c +10 -0
+
+	/* --- win32 type paths --- */
+	{ "\\usr",           "usr"  },
+	{ "\\usr\\",         "usr"  },
+	{ "\\usr\\\\",       "usr"  },
+	{ "\\usr\\lib",      "lib"  },
+	{ "usr\\lib",        "lib"  },
+	{ "usr\\lib\\\\\\",  "lib"  },
+	{ "C:/usr",          "usr"  },
+	{ "C:/usr",          "usr"  },

This duplication was in the test-libgen.c file I sent you ... so, my bad. ;-)

Did you not have more tests to add?

ATB,
Ramsay Jones

#51 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Hi Ramsay,

Quoted 1 line · Ramsay Jones

Did you not have more tests to add?

No, all my testing was manual so far, and well covered by your test cases.

Thanks,
Dscho

A message that is not in the archive (sent off-list or not yet received): [email protected]

#53 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Hi Torsten,

Quoted 12 lines · Torsten Bögershausen

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 77

Quoted 8 lines · Johannes Schindelin
test-path-utils.c +8 -0
+static struct test_data basename_data[] = {
+	/* --- POSIX type paths --- */
+	{ NULL,              "."    },
+	{ "",                "."    },
+	{ ".",               "."    },
+	{ "..",              ".."   },
+	{ "/",               "/"    },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)

Why the !defined(NO_LIBGEN_H)

Quoted 1 line

Shouldn't CYGWIN always behave the same ?

One would assume... Alas, it does not.

I inherited the code in question and wondered the same. I opted for keeping the code as a documentation of the differing behavior.

Quoted 4 lines

The main problem is, that t0060 fails under Mac OS (with mac ports installed):
expecting success: test-path-utils dirname
error: FAIL: dirname(//) => '/' != '//'

See the patch I sent this morning.

Ciao,
Dscho

#54 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Quoted 16 lines · Johannes Schindelin

Hi Torsten,

Quoted 12 lines · Torsten Bögershausen

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 77

Quoted 8 lines · Johannes Schindelin
test-path-utils.c +8 -0
+static struct test_data basename_data[] = {
+	/* --- POSIX type paths --- */
+	{ NULL,              "."    },
+	{ "",                "."    },
+	{ ".",               "."    },
+	{ "..",              ".."   },
+	{ "/",               "/"    },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)

Why the !defined(NO_LIBGEN_H)

Quoted 3 lines
Quoted 1 line

Shouldn't CYGWIN always behave the same ?

One would assume... Alas, it does not.

Err, ... yes it does! :-P

Quoted 3 lines

I inherited the code in question and wondered the same. I opted for keeping the code as a documentation of the differing behavior.

Exactly.

ATB,
Ramsay Jones

#55 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

In reply to a message not in the archive
Quoted 2 lines · Torsten Bögershausen

On 01/12/2016 08:57 AM, Johannes Schindelin wrote:

[snip]

Quoted 10 lines

test-path-utils.c static void normalize_argv_string(const char **var, const char *input) · line 77

Quoted 9 lines
test-path-utils.c +9 -0
+
+static struct test_data basename_data[] = {
+	/* --- POSIX type paths --- */
+	{ NULL,              "."    },
+	{ "",                "."    },
+	{ ".",               "."    },
+	{ "..",              ".."   },
+	{ "/",               "/"    },
+#if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)

Why the !defined(NO_LIBGEN_H)

These tests were derived from a standalone test program which I was using to test my implementation of gitbasename() *and* to document the differences between it and the system versions of the <libgen.h> basename(). (I didn't bother with the GNU version of basename, which is somewhat strange).

This particular section documents what is almost certainly a bug in the cygwin basename() and also documents my choice of 'fix'. (ie. in my implementation I chose to return '/' for '//', which is one of the possible options that POSIX allows.)

Quoted 3 lines

Shouldn't CYGWIN always behave the same ?
And, in general, shouldn't all Windows version behave the same ?

Hmm, cygwin is not really a 'Windows version', so ... (We have been caught out before by cygwin 'supporting' UNC paths, so support for '//' is open to question. Also, some git programs on cygwin kinda sorta support dos paths ...)

Quoted 2 lines

(This would mean, that we always use ../compat/basename.c for all kind of Windows Git implementattion. Would there be a drawback ?)

And maybe not just Windows ...

Quoted 5 lines

The main problem is, that t0060 fails under Mac OS (with mac ports installed):
expecting success: test-path-utils dirname
error: FAIL: dirname(//) => '/' != '//'

Yep, not surprised. Again that test file was developed and tested on only the five platforms available to me at the time, namely: Linux (both 32 and 64bit), Windows XP 32-bit (MSVC), MinGW 32-bit and Cygwin 32-bit.

POSIX says, in part [1]:

If the string pointed to by path consists entirely of the '/' character, basename() shall return a pointer to the string "/". If the string pointed to by path is exactly "//", it is implementation-defined whether '/' or "//" is returned.

[1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html

So we should expect other systems to differ, even if they support POSIX. (and maybe not just this test case.)

Quoted 9 lines

not ok 2 - dirname
# test-path-utils dirname

To my understanding the treatment of a path name like "//"
is defined as "undefined":
If /string/ is "//", it is implementation-defined whether steps 3 to 6 are skipped or processed.
http://pubs.opengroup.org/onlinepubs/009695399/utilities/basename.html

Quoted 9 lines

What I understand is that a path like "//XX/YY/ZZ" can be handled in 3 different ways:
a) Same as "/XX/YY/ZZ", silently turning "//" into "/"
b) Same as "\\XX\YY\ZZ", using UNC names under Windows. # Note: this reads as "\\\\XX\\YY\\ZZ" in a C program
c) As invalid

Does it make sense to use the compat/basename.c for all Git implementations ? (Or at least for CYGWIN, Mac OS, MSYS, MSVC)

Maybe, I hadn't got that far yet ... :-D

ATB,
Ramsay Jones

#56 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Quoted 12 lines · Ramsay Jones

This particular section documents what is almost certainly a bug in the cygwin basename() and also documents my choice of 'fix'. (ie. in my implementation I chose to return '/' for '//', which is one of the possible options that POSIX allows.)
...
POSIX says, in part [1]:

If the string pointed to by path consists entirely of the '/' character, basename() shall return a pointer to the string "/". If the string pointed to by path is exactly "//", it is implementation-defined whether '/' or "//" is returned.

Quoted 4 lines

[1] http://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html

So we should expect other systems to differ, even if they support POSIX. (and maybe not just this test case.)

Doesn't that mean the test shouldn't be insisting on the output being one that you arbitrarily pick? It feels to me that it is wrong to say "We require // to become / unless we know we are on such and such systems". Instead, shouldn't it be doing "We feed // to the function. Either / or // is acceptable; any other value is a bug"?

It is tempting to have a "check" feature to the test program for the curious which one of the two acceptable results a particular platform (or more precisely, implementation of basename/dirname) produces, but I do not think "test" feature should insist on / and reject //.

#57 lore raw

Re: [PATCH v4 4/4] t0060: verify that basename() and dirname() work as expected

Hi Junio,

Quoted 20 lines · Junio C Hamano
Quoted 12 lines · Ramsay Jones

This particular section documents what is almost certainly a bug in the cygwin basename() and also documents my choice of 'fix'. (ie. in my implementation I chose to return '/' for '//', which is one of the possible options that POSIX allows.)
...
POSIX says, in part [1]:

If the string pointed to by path consists entirely of the '/' character, basename() shall return a pointer to the string "/". If the string pointed to by path is exactly "//", it is implementation-defined whether '/' or "//" is returned.

Quoted 5 lines

[1]
http://pubs.opengroup.org/onlinepubs/9699919799/functions/basename.html

So we should expect other systems to differ, even if they support POSIX. (and maybe not just this test case.)

Quoted 6 lines

Doesn't that mean the test shouldn't be insisting on the output being one that you arbitrarily pick? It feels to me that it is wrong to say "We require // to become / unless we know we are on such and such systems". Instead, shouldn't it be doing "We feed // to the function. Either / or // is acceptable; any other value is a bug"?

I guess that is the best solution of all. I'll try to modify test-path-utils.c accordingly tomorrow.

Ciao,
Dscho

#58 lore raw

Re: [PATCH v4 0/4] Ensure that we can build without libgen.h

In reply to Johannes Schindelin

Hi Johannes,

Sorry for not commenting sooner, I've been away from email for a few days. Also, I have only just looked at what is currently in pu (@1a05310), which I'm pretty sure is v3 of this series.

Quoted 6 lines · Johannes Schindelin

This mini series adds a fall-back for the dirname() function that we use e.g. in git-am. This is necessary because not all platforms have a working libgen.h.

While at it, we ensure that our basename() drop-in conforms to the POSIX specifications.

I was somewhat disappointed that you ignored the implementation of gitbasename() and gitdirname() that was included in the test-libgen.c file that I sent you. I had hoped they would be (at worst) a good starting point if you found them to be lacking for your use case (ie. for the 64-bit versions of MSVC/MinGW).

Did you have any test cases that failed? (If so, could you please add them to the tests).

Hmm, I just had another look at them and recalled one of my TODO items. Ahem, yes, ... err, replace code which provoked undefined behaviour. :-P

Actually, that took just ten minutes to fix. (patch below)

Quoted 3 lines

In addition to Eric's style improvement, v4 also fixes the signature of skip_dos_drive_prefix() in the non-Windows case.

Yes, this fixes one of my comments about v3.

ATB,
Ramsay Jones
-- >8 --
From: Ramsay Jones <[email protected]>
Date: Tue, 12 Jan 2016 23:28:09 +0000
Subject: [PATCH] test-libgen.c: don't provoke undefined behaviour

---
 test-libgen.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)
test-libgen.c +7 -6
index aa3bd18..3024bf1 100644
@@ -42,9 +42,11 @@ char *gitbasename (char *path)
 		*p-- = '\0';
 	}
 	/* find begining of last path component */
-	while (p >= path && !is_dir_sep(*p))
+	while (p > path && !is_dir_sep(*p))
 		p--;
-	return p + 1;
+	if (is_dir_sep(*p))
+		p++;
+	return p;
 }
 
 char *gitdirname(char *path)
@@ -71,13 +73,12 @@ char *gitdirname(char *path)
 		*p-- = '\0';
 	}
 	/* find begining of last path component */
-	while (p >= start && !is_dir_sep(*p))
+	while (p > start && !is_dir_sep(*p))
 		p--;
 	/* terminate dirname */
-	if (p < start) {
-		p = start;
+	if (p == start && !is_dir_sep(*p))
 		*p++ = '.';
-	} else if (p == start)
+	else if (p == start)
 		p++;
 	*p = '\0';
 	return path;
Signature · 1 line
2.7.0
#59 lore raw

Re: [PATCH v4 0/4] Ensure that we can build without libgen.h

Hi Ramsay,

Quoted 11 lines · Ramsay Jones
Quoted 6 lines · Johannes Schindelin

This mini series adds a fall-back for the dirname() function that we use e.g. in git-am. This is necessary because not all platforms have a working libgen.h.

While at it, we ensure that our basename() drop-in conforms to the POSIX specifications.

I was somewhat disappointed that you ignored the implementation of gitbasename() and gitdirname() that was included in the test-libgen.c file that I sent you.

I am sorry you feel that I ignored your work!

My line of reasoning, however, was to go with the existing gitbasename() and with the gitdirname() I had come up with, because I was already familiar with them.

Your tests included a couple of corner cases that neither handled correctly, and I was able to fix that, so I was happy.

To be quite honest, I blindly deleted everything but the tests, noticed that the remaining code looked eerily similar to test-path-utils, and merged it there.

Ciao,
Dscho

#60 lore raw

Re: [PATCH v4 0/4] Ensure that we can build without libgen.h

In reply to Ramsay Jones
Quoted 7 lines · Ramsay Jones

Hi Johannes,
...
I was somewhat disappointed that you ignored the implementation of gitbasename() and gitdirname() that was included in the test-libgen.c file that I sent you. I had hoped they would be (at worst) a good starting point if you found them to be lacking for your use case (ie. for the 64-bit versions of MSVC/MinGW).

Sorry to hear that, but the 'next' branch has just been rewound and rebuilt with this series, so it is too late to replace them with another round of reroll. It however is never too late to improve with incremental updates, though, so please work together and send in a follow-up patch series as/if needed.

Thanks.

#61 lore raw

Re: [PATCH v4 0/4] Ensure that we can build without libgen.h

Quoted 9 lines · Junio C Hamano
Quoted 7 lines · Ramsay Jones

Hi Johannes,
...
I was somewhat disappointed that you ignored the implementation of gitbasename() and gitdirname() that was included in the test-libgen.c file that I sent you. I had hoped they would be (at worst) a good starting point if you found them to be lacking for your use case (ie. for the 64-bit versions of MSVC/MinGW).

Quoted 6 lines

Sorry to hear that, but the 'next' branch has just been rewound and rebuilt with this series, so it is too late to replace them with another round of reroll. It however is never too late to improve with incremental updates, though, so please work together and send in a follow-up patch series as/if needed.

Is there a chance to keep it in next (and not merge to master) until we have found a solution for the broken t0060 test under Mac OS X ?

#62 lore raw

Re: [PATCH v4 0/4] Ensure that we can build without libgen.h

Hi Torsten,

Quoted 11 lines · Torsten Bögershausen
Quoted 10 lines · Junio C Hamano
Quoted 8 lines · Ramsay Jones

Hi Johannes,
...
I was somewhat disappointed that you ignored the implementation of gitbasename() and gitdirname() that was included in the test-libgen.c file that I sent you. I had hoped they would be (at worst) a good starting
point if you found them to be lacking for your use case (ie. for the 64-bit versions of MSVC/MinGW).

Quoted 8 lines
Quoted 6 lines

Sorry to hear that, but the 'next' branch has just been rewound and rebuilt with this series, so it is too late to replace them with another round of reroll. It however is never too late to improve with incremental updates, though, so please work together and send in a follow-up patch series as/if needed.

Is there a chance to keep it in next (and not merge to master) until we have found a solution for the broken t0060 test under Mac OS X ?

This is actually independent of follow-up patches that might replace the gitbasename()/gitdirname() functions wholesale, as the problem you encountered is *differing* behavior: no matter what implementation of gitdirname() we use, it will either agree with dirname() on Linux or with dirname() on MacOSX, it cannot do both.

So here is a hot-fix (Junio, would you kindly apply it?):

-- snipsnap --
Subject: [PATCH] t0060: fix dirname test on MacOSX

Contrary to this developer's assumptions, the behavior of dirname("//") is not consistent between platforms. Let's document this by not removing this test case, but showing that it behaves differently on MacOSX (unless NO_LIBGEN_H is defined, that is).

---
 test-path-utils.c | 4 ++++
 1 file changed, 4 insertions(+)
test-path-utils.c +4 -0
index 4ab68ac..a3a69ce 100644
@@ -142,7 +142,11 @@ static struct test_data dirname_data[] = {
 	{ ".",               "."      },
 	{ "..",              "."      },
 	{ "/",               "/"      },
+#if defined(__APPLE__) && !defined(NO_LIBGEN_H)
+	{ "//",              "/"     },
+#else
 	{ "//",              "//"     },
+#endif
 #if defined(__CYGWIN__) && !defined(NO_LIBGEN_H)
 	{ "///",             "//"     },
 	{ "////",            "//"     },
Signature · 1 line
2.6.3.windows.1.300.g1c25e49