mirror of
https://github.com/git-for-windows/git.git
synced 2026-06-27 00:58:30 -05:00
The Git project is not exactly the easiest project to get started in: it's written in C and POSIX shell, with bits of Perl, Rust and other languages sprinkled into it. On top of that, the project has grown somewhat organically over time, making the codebase hard to navigate. These are problems that we're aware of, and there have been and still are efforts to clean up some of the technical debt that is natural to exist an a project that is more than 20 years old. Furthermore, we provide resources to newcomers that help them out like our coding guidelines, code of conduct or "MyFirstContribution.adoc". But there is a rather practical problem: finding your way around in our project's tree is not easy. Doing a directory listing in the top-level directory will present you with more than 550 files, which makes it extremely hard for a newcomer to figure out what files they are even supposed to look at. This makes the onboarding experience somewhat harder than it really needs to be. This isn't only a problem for newcomers though, as I myself struggle to find the files I am looking for because of the sheer number of files. Besides the problem of discoverability it also creates a problem of structure. It is not obvious at all which files are part of "libgit.a" and which files are only linked into our final executables. So while we have this split in our build systems, that split is not evident at all in our tree. Introduce a new "lib/" directory and move all of our sources for "libgit.a" into it to fix these issues. It makes the split we have evident and reduces the number of files in our top-level tree from 550 files to ~80 files. This is still a lot of files, but it's significantly easier to navigate already. Furthermore, we can further iterate after this step and think about introducing a better structure for remaining files, as well. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
570 lines
12 KiB
C
570 lines
12 KiB
C
#define DISABLE_SIGN_COMPARE_WARNINGS
|
|
|
|
#include "git-compat-util.h"
|
|
#include "path.h"
|
|
#include "quote.h"
|
|
#include "strbuf.h"
|
|
#include "strvec.h"
|
|
|
|
int quote_path_fully = 1;
|
|
|
|
static inline int need_bs_quote(char c)
|
|
{
|
|
return (c == '\'' || c == '!');
|
|
}
|
|
|
|
/* Help to copy the thing properly quoted for the shell safety.
|
|
* any single quote is replaced with '\'', any exclamation point
|
|
* is replaced with '\!', and the whole thing is enclosed in a
|
|
* single quote pair.
|
|
*
|
|
* E.g.
|
|
* original sq_quote result
|
|
* name ==> name ==> 'name'
|
|
* a b ==> a b ==> 'a b'
|
|
* a'b ==> a'\''b ==> 'a'\''b'
|
|
* a!b ==> a'\!'b ==> 'a'\!'b'
|
|
*/
|
|
void sq_quote_buf(struct strbuf *dst, const char *src)
|
|
{
|
|
char *to_free = NULL;
|
|
|
|
if (dst->buf == src)
|
|
to_free = strbuf_detach(dst, NULL);
|
|
|
|
strbuf_addch(dst, '\'');
|
|
while (*src) {
|
|
size_t len = strcspn(src, "'!");
|
|
strbuf_add(dst, src, len);
|
|
src += len;
|
|
while (need_bs_quote(*src)) {
|
|
strbuf_addstr(dst, "'\\");
|
|
strbuf_addch(dst, *src++);
|
|
strbuf_addch(dst, '\'');
|
|
}
|
|
}
|
|
strbuf_addch(dst, '\'');
|
|
free(to_free);
|
|
}
|
|
|
|
void sq_quote_buf_pretty(struct strbuf *dst, const char *src)
|
|
{
|
|
static const char ok_punct[] = "+,-./:=@_^";
|
|
const char *p;
|
|
|
|
/* Avoid losing a zero-length string by adding '' */
|
|
if (!*src) {
|
|
strbuf_addstr(dst, "''");
|
|
return;
|
|
}
|
|
|
|
for (p = src; *p; p++) {
|
|
if (!isalnum(*p) && !strchr(ok_punct, *p)) {
|
|
sq_quote_buf(dst, src);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* if we get here, we did not need quoting */
|
|
strbuf_addstr(dst, src);
|
|
}
|
|
|
|
void sq_quotef(struct strbuf *dst, const char *fmt, ...)
|
|
{
|
|
struct strbuf src = STRBUF_INIT;
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
strbuf_vaddf(&src, fmt, ap);
|
|
va_end(ap);
|
|
|
|
sq_quote_buf(dst, src.buf);
|
|
strbuf_release(&src);
|
|
}
|
|
|
|
void sq_quote_argv(struct strbuf *dst, const char **argv)
|
|
{
|
|
int i;
|
|
|
|
/* Copy into destination buffer. */
|
|
strbuf_grow(dst, 255);
|
|
for (i = 0; argv[i]; ++i) {
|
|
strbuf_addch(dst, ' ');
|
|
sq_quote_buf(dst, argv[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Legacy function to append each argv value, quoted as necessasry,
|
|
* with whitespace before each value. This results in a leading
|
|
* space in the result.
|
|
*/
|
|
void sq_quote_argv_pretty(struct strbuf *dst, const char **argv)
|
|
{
|
|
if (argv[0])
|
|
strbuf_addch(dst, ' ');
|
|
sq_append_quote_argv_pretty(dst, argv);
|
|
}
|
|
|
|
/*
|
|
* Append each argv value, quoted as necessary, with whitespace between them.
|
|
*/
|
|
void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; argv[i]; i++) {
|
|
if (i > 0)
|
|
strbuf_addch(dst, ' ');
|
|
sq_quote_buf_pretty(dst, argv[i]);
|
|
}
|
|
}
|
|
|
|
char *sq_dequote_step(char *arg, char **next)
|
|
{
|
|
char *dst = arg;
|
|
char *src = arg;
|
|
char c;
|
|
|
|
if (*src != '\'')
|
|
return NULL;
|
|
for (;;) {
|
|
c = *++src;
|
|
if (!c)
|
|
return NULL;
|
|
if (c != '\'') {
|
|
*dst++ = c;
|
|
continue;
|
|
}
|
|
/* We stepped out of sq */
|
|
switch (*++src) {
|
|
case '\0':
|
|
*dst = 0;
|
|
if (next)
|
|
*next = NULL;
|
|
return arg;
|
|
case '\\':
|
|
/*
|
|
* Allow backslashed characters outside of
|
|
* single-quotes only if they need escaping,
|
|
* and only if we resume the single-quoted part
|
|
* afterward.
|
|
*/
|
|
if (need_bs_quote(src[1]) && src[2] == '\'') {
|
|
*dst++ = src[1];
|
|
src += 2;
|
|
continue;
|
|
}
|
|
/* Fallthrough */
|
|
default:
|
|
if (!next)
|
|
return NULL;
|
|
*dst = 0;
|
|
*next = src;
|
|
return arg;
|
|
}
|
|
}
|
|
}
|
|
|
|
char *sq_dequote(char *arg)
|
|
{
|
|
return sq_dequote_step(arg, NULL);
|
|
}
|
|
|
|
int sq_dequote_to_strvec(char *arg, struct strvec *array)
|
|
{
|
|
char *next = arg;
|
|
|
|
if (!*arg)
|
|
return 0;
|
|
do {
|
|
char *dequoted = sq_dequote_step(next, &next);
|
|
if (!dequoted)
|
|
return -1;
|
|
if (next) {
|
|
char c;
|
|
if (!isspace(*next))
|
|
return -1;
|
|
do {
|
|
c = *++next;
|
|
} while (isspace(c));
|
|
}
|
|
strvec_push(array, dequoted);
|
|
} while (next);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* 1 means: quote as octal
|
|
* 0 means: quote as octal if (quote_path_fully)
|
|
* -1 means: never quote
|
|
* c: quote as "\\c"
|
|
*/
|
|
#define X8(x) x, x, x, x, x, x, x, x
|
|
#define X16(x) X8(x), X8(x)
|
|
static signed char const cq_lookup[256] = {
|
|
/* 0 1 2 3 4 5 6 7 */
|
|
/* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a',
|
|
/* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1,
|
|
/* 0x10 */ X16(1),
|
|
/* 0x20 */ -1, -1, '"', -1, -1, -1, -1, -1,
|
|
/* 0x28 */ X16(-1), X16(-1), X16(-1),
|
|
/* 0x58 */ -1, -1, -1, -1,'\\', -1, -1, -1,
|
|
/* 0x60 */ X16(-1), X8(-1),
|
|
/* 0x78 */ -1, -1, -1, -1, -1, -1, -1, 1,
|
|
/* 0x80 */ /* set to 0 */
|
|
};
|
|
|
|
static inline int cq_must_quote(char c)
|
|
{
|
|
return cq_lookup[(unsigned char)c] + quote_path_fully > 0;
|
|
}
|
|
|
|
/* returns the longest prefix not needing a quote up to maxlen if positive.
|
|
This stops at the first \0 because it's marked as a character needing an
|
|
escape */
|
|
static size_t next_quote_pos(const char *s, ssize_t maxlen)
|
|
{
|
|
size_t len;
|
|
if (maxlen < 0) {
|
|
for (len = 0; !cq_must_quote(s[len]); len++);
|
|
} else {
|
|
for (len = 0; len < maxlen && !cq_must_quote(s[len]); len++);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* C-style name quoting.
|
|
*
|
|
* (1) if sb and fp are both NULL, inspect the input name and counts the
|
|
* number of bytes that are needed to hold c_style quoted version of name,
|
|
* counting the double quotes around it but not terminating NUL, and
|
|
* returns it.
|
|
* However, if name does not need c_style quoting, it returns 0.
|
|
*
|
|
* (2) if sb or fp are not NULL, it emits the c_style quoted version
|
|
* of name, enclosed with double quotes if asked and needed only.
|
|
* Return value is the same as in (1).
|
|
*/
|
|
static size_t quote_c_style_counted(const char *name, ssize_t maxlen,
|
|
struct strbuf *sb, FILE *fp, unsigned flags)
|
|
{
|
|
#undef EMIT
|
|
#define EMIT(c) \
|
|
do { \
|
|
if (sb) strbuf_addch(sb, (c)); \
|
|
if (fp) fputc((c), fp); \
|
|
count++; \
|
|
} while (0)
|
|
#define EMITBUF(s, l) \
|
|
do { \
|
|
if (sb) strbuf_add(sb, (s), (l)); \
|
|
if (fp) fwrite((s), (l), 1, fp); \
|
|
count += (l); \
|
|
} while (0)
|
|
|
|
int no_dq = !!(flags & CQUOTE_NODQ);
|
|
size_t len, count = 0;
|
|
const char *p = name;
|
|
|
|
for (;;) {
|
|
int ch;
|
|
|
|
len = next_quote_pos(p, maxlen);
|
|
if (len == maxlen || (maxlen < 0 && !p[len]))
|
|
break;
|
|
|
|
if (!no_dq && p == name)
|
|
EMIT('"');
|
|
|
|
EMITBUF(p, len);
|
|
EMIT('\\');
|
|
p += len;
|
|
ch = (unsigned char)*p++;
|
|
if (maxlen >= 0)
|
|
maxlen -= len + 1;
|
|
if (cq_lookup[ch] >= ' ') {
|
|
EMIT(cq_lookup[ch]);
|
|
} else {
|
|
EMIT(((ch >> 6) & 03) + '0');
|
|
EMIT(((ch >> 3) & 07) + '0');
|
|
EMIT(((ch >> 0) & 07) + '0');
|
|
}
|
|
}
|
|
|
|
EMITBUF(p, len);
|
|
if (p == name) /* no ending quote needed */
|
|
return 0;
|
|
|
|
if (!no_dq)
|
|
EMIT('"');
|
|
return count;
|
|
}
|
|
|
|
size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, unsigned flags)
|
|
{
|
|
return quote_c_style_counted(name, -1, sb, fp, flags);
|
|
}
|
|
|
|
void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path,
|
|
unsigned flags)
|
|
{
|
|
int nodq = !!(flags & CQUOTE_NODQ);
|
|
if (quote_c_style(prefix, NULL, NULL, 0) ||
|
|
quote_c_style(path, NULL, NULL, 0)) {
|
|
if (!nodq)
|
|
strbuf_addch(sb, '"');
|
|
quote_c_style(prefix, sb, NULL, CQUOTE_NODQ);
|
|
quote_c_style(path, sb, NULL, CQUOTE_NODQ);
|
|
if (!nodq)
|
|
strbuf_addch(sb, '"');
|
|
} else {
|
|
strbuf_addstr(sb, prefix);
|
|
strbuf_addstr(sb, path);
|
|
}
|
|
}
|
|
|
|
void write_name_quoted(const char *name, FILE *fp, int terminator)
|
|
{
|
|
if (terminator) {
|
|
quote_c_style(name, NULL, fp, 0);
|
|
} else {
|
|
fputs(name, fp);
|
|
}
|
|
fputc(terminator, fp);
|
|
}
|
|
|
|
void write_name_quoted_relative(const char *name, const char *prefix,
|
|
FILE *fp, int terminator)
|
|
{
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
name = relative_path(name, prefix, &sb);
|
|
write_name_quoted(name, fp, terminator);
|
|
|
|
strbuf_release(&sb);
|
|
}
|
|
|
|
/* quote path as relative to the given prefix */
|
|
char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags)
|
|
{
|
|
struct strbuf sb = STRBUF_INIT;
|
|
const char *rel = relative_path(in, prefix, &sb);
|
|
int force_dq = ((flags & QUOTE_PATH_QUOTE_SP) && strchr(rel, ' '));
|
|
|
|
strbuf_reset(out);
|
|
|
|
/*
|
|
* If the caller wants us to enclose the output in a dq-pair
|
|
* whether quote_c_style_counted() needs to, we do it ourselves
|
|
* and tell quote_c_style_counted() not to.
|
|
*/
|
|
if (force_dq)
|
|
strbuf_addch(out, '"');
|
|
quote_c_style_counted(rel, strlen(rel), out, NULL,
|
|
force_dq ? CQUOTE_NODQ : 0);
|
|
if (force_dq)
|
|
strbuf_addch(out, '"');
|
|
strbuf_release(&sb);
|
|
|
|
return out->buf;
|
|
}
|
|
|
|
/*
|
|
* C-style name unquoting.
|
|
*
|
|
* Quoted should point at the opening double quote.
|
|
* + Returns 0 if it was able to unquote the string properly, and appends the
|
|
* result in the strbuf `sb'.
|
|
* + Returns -1 in case of error, and doesn't touch the strbuf. Though note
|
|
* that this function will allocate memory in the strbuf, so calling
|
|
* strbuf_release is mandatory whichever result unquote_c_style returns.
|
|
*
|
|
* Updates endp pointer to point at one past the ending double quote if given.
|
|
*/
|
|
int unquote_c_style(struct strbuf *sb, const char *quoted, const char **endp)
|
|
{
|
|
size_t oldlen = sb->len, len;
|
|
int ch, ac;
|
|
|
|
if (*quoted++ != '"')
|
|
return -1;
|
|
|
|
for (;;) {
|
|
len = strcspn(quoted, "\"\\");
|
|
strbuf_add(sb, quoted, len);
|
|
quoted += len;
|
|
|
|
switch (*quoted++) {
|
|
case '"':
|
|
if (endp)
|
|
*endp = quoted;
|
|
return 0;
|
|
case '\\':
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
|
|
switch ((ch = *quoted++)) {
|
|
case 'a': ch = '\a'; break;
|
|
case 'b': ch = '\b'; break;
|
|
case 'f': ch = '\f'; break;
|
|
case 'n': ch = '\n'; break;
|
|
case 'r': ch = '\r'; break;
|
|
case 't': ch = '\t'; break;
|
|
case 'v': ch = '\v'; break;
|
|
|
|
case '\\': case '"':
|
|
break; /* verbatim */
|
|
|
|
/* octal values with first digit over 4 overflow */
|
|
case '0': case '1': case '2': case '3':
|
|
ac = ((ch - '0') << 6);
|
|
if ((ch = *quoted++) < '0' || '7' < ch)
|
|
goto error;
|
|
ac |= ((ch - '0') << 3);
|
|
if ((ch = *quoted++) < '0' || '7' < ch)
|
|
goto error;
|
|
ac |= (ch - '0');
|
|
ch = ac;
|
|
break;
|
|
default:
|
|
goto error;
|
|
}
|
|
strbuf_addch(sb, ch);
|
|
}
|
|
|
|
error:
|
|
strbuf_setlen(sb, oldlen);
|
|
return -1;
|
|
}
|
|
|
|
/* quoting as a string literal for other languages */
|
|
|
|
void perl_quote_buf(struct strbuf *sb, const char *src)
|
|
{
|
|
const char sq = '\'';
|
|
const char bq = '\\';
|
|
char c;
|
|
|
|
strbuf_addch(sb, sq);
|
|
while ((c = *src++)) {
|
|
if (c == sq || c == bq)
|
|
strbuf_addch(sb, bq);
|
|
strbuf_addch(sb, c);
|
|
}
|
|
strbuf_addch(sb, sq);
|
|
}
|
|
|
|
void perl_quote_buf_with_len(struct strbuf *sb, const char *src, size_t len)
|
|
{
|
|
const char sq = '\'';
|
|
const char bq = '\\';
|
|
const char *c = src;
|
|
const char *end = src + len;
|
|
|
|
strbuf_addch(sb, sq);
|
|
while (c != end) {
|
|
if (*c == sq || *c == bq)
|
|
strbuf_addch(sb, bq);
|
|
strbuf_addch(sb, *c);
|
|
c++;
|
|
}
|
|
strbuf_addch(sb, sq);
|
|
}
|
|
|
|
void python_quote_buf(struct strbuf *sb, const char *src)
|
|
{
|
|
const char sq = '\'';
|
|
const char bq = '\\';
|
|
const char nl = '\n';
|
|
char c;
|
|
|
|
strbuf_addch(sb, sq);
|
|
while ((c = *src++)) {
|
|
if (c == nl) {
|
|
strbuf_addch(sb, bq);
|
|
strbuf_addch(sb, 'n');
|
|
continue;
|
|
}
|
|
if (c == sq || c == bq)
|
|
strbuf_addch(sb, bq);
|
|
strbuf_addch(sb, c);
|
|
}
|
|
strbuf_addch(sb, sq);
|
|
}
|
|
|
|
void tcl_quote_buf(struct strbuf *sb, const char *src)
|
|
{
|
|
char c;
|
|
|
|
strbuf_addch(sb, '"');
|
|
while ((c = *src++)) {
|
|
switch (c) {
|
|
case '[': case ']':
|
|
case '{': case '}':
|
|
case '$': case '\\': case '"':
|
|
strbuf_addch(sb, '\\');
|
|
/* fallthrough */
|
|
default:
|
|
strbuf_addch(sb, c);
|
|
break;
|
|
case '\f':
|
|
strbuf_addstr(sb, "\\f");
|
|
break;
|
|
case '\r':
|
|
strbuf_addstr(sb, "\\r");
|
|
break;
|
|
case '\n':
|
|
strbuf_addstr(sb, "\\n");
|
|
break;
|
|
case '\t':
|
|
strbuf_addstr(sb, "\\t");
|
|
break;
|
|
case '\v':
|
|
strbuf_addstr(sb, "\\v");
|
|
break;
|
|
}
|
|
}
|
|
strbuf_addch(sb, '"');
|
|
}
|
|
|
|
void basic_regex_quote_buf(struct strbuf *sb, const char *src)
|
|
{
|
|
char c;
|
|
|
|
if (*src == '^') {
|
|
/* only beginning '^' is special and needs quoting */
|
|
strbuf_addch(sb, '\\');
|
|
strbuf_addch(sb, *src++);
|
|
}
|
|
if (*src == '*')
|
|
/* beginning '*' is not special, no quoting */
|
|
strbuf_addch(sb, *src++);
|
|
|
|
while ((c = *src++)) {
|
|
switch (c) {
|
|
case '[':
|
|
case '.':
|
|
case '\\':
|
|
case '*':
|
|
strbuf_addch(sb, '\\');
|
|
strbuf_addch(sb, c);
|
|
break;
|
|
|
|
case '$':
|
|
/* only the end '$' is special and needs quoting */
|
|
if (*src == '\0')
|
|
strbuf_addch(sb, '\\');
|
|
strbuf_addch(sb, c);
|
|
break;
|
|
|
|
default:
|
|
strbuf_addch(sb, c);
|
|
break;
|
|
}
|
|
}
|
|
}
|