Subject: Re: [PATCH v2 3/4] Provide a dirname() function when NO_LIBGEN_H=YesPlease

From: Junio C Hamano <[email-masked]>
Date: Fri, 08 Jan 2016 10:53:40 -0800

Johannes Schindelin <[email-masked]> writes:

> 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.
>
> Signed-off-by: Johannes Schindelin <[email-masked]>
> ---
>  compat/basename.c | 44 ++++++++++++++++++++++++++++++++++++++++++++
>  git-compat-util.h |  2 ++
>  2 files changed, 46 insertions(+)
>
> diff --git a/compat/basename.c b/compat/basename.c
> index 0f1b0b0..0a2ed25 100644
> --- a/compat/basename.c
> +++ b/compat/basename.c
> @@ -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);

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

> +		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;
> +		}

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.

> +
> +	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.

> +	*slash = '\0';
> +	return path;
> +}
> diff --git a/git-compat-util.h b/git-compat-util.h
> index 0d66f3a..94f311a 100644
> --- a/git-compat-util.h
> +++ b/git-compat-util.h
> @@ -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