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>
498 lines
12 KiB
C
498 lines
12 KiB
C
#define USE_THE_REPOSITORY_VARIABLE
|
|
#define DISABLE_SIGN_COMPARE_WARNINGS
|
|
|
|
#include "git-compat-util.h"
|
|
#include "gettext.h"
|
|
#include "hash.h"
|
|
#include "hex.h"
|
|
#include "string-list.h"
|
|
#include "strvec.h"
|
|
#include "refs.h"
|
|
#include "refspec.h"
|
|
#include "remote.h"
|
|
#include "strbuf.h"
|
|
|
|
/*
|
|
* Parses the provided refspec 'refspec' and populates the refspec_item 'item'.
|
|
* Returns 1 if successful and 0 if the refspec is invalid.
|
|
*/
|
|
static int parse_refspec(struct refspec_item *item, const char *refspec, int fetch)
|
|
{
|
|
size_t llen;
|
|
int is_glob;
|
|
const char *lhs, *rhs;
|
|
int flags;
|
|
|
|
is_glob = 0;
|
|
|
|
lhs = refspec;
|
|
if (*lhs == '+') {
|
|
item->force = 1;
|
|
lhs++;
|
|
} else if (*lhs == '^') {
|
|
item->negative = 1;
|
|
lhs++;
|
|
}
|
|
|
|
rhs = strrchr(lhs, ':');
|
|
|
|
/* negative refspecs only have one side */
|
|
if (item->negative && rhs)
|
|
return 0;
|
|
|
|
/*
|
|
* Before going on, special case ":" (or "+:") as a refspec
|
|
* for pushing matching refs.
|
|
*/
|
|
if (!fetch && rhs == lhs && rhs[1] == '\0') {
|
|
item->matching = 1;
|
|
return 1;
|
|
}
|
|
|
|
if (rhs) {
|
|
size_t rlen = strlen(++rhs);
|
|
is_glob = (1 <= rlen && strchr(rhs, '*'));
|
|
item->dst = xstrndup(rhs, rlen);
|
|
} else {
|
|
item->dst = NULL;
|
|
}
|
|
|
|
llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
|
|
if (1 <= llen && memchr(lhs, '*', llen)) {
|
|
if ((rhs && !is_glob) || (!rhs && !item->negative && fetch))
|
|
return 0;
|
|
is_glob = 1;
|
|
} else if (rhs && is_glob) {
|
|
return 0;
|
|
}
|
|
|
|
item->pattern = is_glob;
|
|
if (llen == 1 && *lhs == '@')
|
|
item->src = xstrdup("HEAD");
|
|
else
|
|
item->src = xstrndup(lhs, llen);
|
|
flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
|
|
|
|
if (item->negative) {
|
|
struct object_id unused;
|
|
|
|
/*
|
|
* Negative refspecs only have a LHS, which indicates a ref
|
|
* (or pattern of refs) to exclude from other matches. This
|
|
* can either be a simple ref, or a glob pattern. Exact sha1
|
|
* match is not currently supported.
|
|
*/
|
|
if (!*item->src)
|
|
return 0; /* negative refspecs must not be empty */
|
|
else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
|
|
return 0; /* negative refspecs cannot be exact sha1 */
|
|
else if (!check_refname_format(item->src, flags))
|
|
; /* valid looking ref is ok */
|
|
else
|
|
return 0;
|
|
|
|
/* the other rules below do not apply to negative refspecs */
|
|
return 1;
|
|
}
|
|
|
|
if (fetch) {
|
|
struct object_id unused;
|
|
|
|
/* LHS */
|
|
if (!*item->src)
|
|
; /* empty is ok; it means "HEAD" */
|
|
else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused))
|
|
item->exact_sha1 = 1; /* ok */
|
|
else if (!check_refname_format(item->src, flags))
|
|
; /* valid looking ref is ok */
|
|
else
|
|
return 0;
|
|
/* RHS */
|
|
if (!item->dst)
|
|
; /* missing is ok; it is the same as empty */
|
|
else if (!*item->dst)
|
|
; /* empty is ok; it means "do not store" */
|
|
else if (!check_refname_format(item->dst, flags))
|
|
; /* valid looking ref is ok */
|
|
else
|
|
return 0;
|
|
} else {
|
|
/*
|
|
* LHS
|
|
* - empty is allowed; it means delete.
|
|
* - when wildcarded, it must be a valid looking ref.
|
|
* - otherwise, it must be an extended SHA-1, but
|
|
* there is no existing way to validate this.
|
|
*/
|
|
if (!*item->src)
|
|
; /* empty is ok */
|
|
else if (is_glob) {
|
|
if (check_refname_format(item->src, flags))
|
|
return 0;
|
|
}
|
|
else
|
|
; /* anything goes, for now */
|
|
/*
|
|
* RHS
|
|
* - missing is allowed, but LHS then must be a
|
|
* valid looking ref.
|
|
* - empty is not allowed.
|
|
* - otherwise it must be a valid looking ref.
|
|
*/
|
|
if (!item->dst) {
|
|
if (check_refname_format(item->src, flags))
|
|
return 0;
|
|
} else if (!*item->dst) {
|
|
return 0;
|
|
} else {
|
|
if (check_refname_format(item->dst, flags))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int refspec_item_init(struct refspec_item *item, const char *refspec,
|
|
int fetch)
|
|
{
|
|
memset(item, 0, sizeof(*item));
|
|
item->raw = xstrdup(refspec);
|
|
return parse_refspec(item, refspec, fetch);
|
|
}
|
|
|
|
int refspec_item_init_fetch(struct refspec_item *item, const char *refspec)
|
|
{
|
|
return refspec_item_init(item, refspec, 1);
|
|
}
|
|
|
|
int refspec_item_init_push(struct refspec_item *item, const char *refspec)
|
|
{
|
|
return refspec_item_init(item, refspec, 0);
|
|
}
|
|
|
|
void refspec_item_clear(struct refspec_item *item)
|
|
{
|
|
FREE_AND_NULL(item->src);
|
|
FREE_AND_NULL(item->dst);
|
|
FREE_AND_NULL(item->raw);
|
|
item->force = 0;
|
|
item->pattern = 0;
|
|
item->matching = 0;
|
|
item->exact_sha1 = 0;
|
|
}
|
|
|
|
void refspec_init_fetch(struct refspec *rs)
|
|
{
|
|
struct refspec blank = REFSPEC_INIT_FETCH;
|
|
memcpy(rs, &blank, sizeof(*rs));
|
|
}
|
|
|
|
void refspec_init_push(struct refspec *rs)
|
|
{
|
|
struct refspec blank = REFSPEC_INIT_PUSH;
|
|
memcpy(rs, &blank, sizeof(*rs));
|
|
}
|
|
|
|
void refspec_append(struct refspec *rs, const char *refspec)
|
|
{
|
|
struct refspec_item item;
|
|
int ret;
|
|
|
|
if (rs->fetch)
|
|
ret = refspec_item_init_fetch(&item, refspec);
|
|
else
|
|
ret = refspec_item_init_push(&item, refspec);
|
|
if (!ret)
|
|
die(_("invalid refspec '%s'"), refspec);
|
|
|
|
ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc);
|
|
rs->items[rs->nr] = item;
|
|
|
|
rs->nr++;
|
|
}
|
|
|
|
void refspec_appendf(struct refspec *rs, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *buf;
|
|
|
|
va_start(ap, fmt);
|
|
buf = xstrvfmt(fmt, ap);
|
|
va_end(ap);
|
|
|
|
refspec_append(rs, buf);
|
|
free(buf);
|
|
}
|
|
|
|
void refspec_appendn(struct refspec *rs, const char **refspecs, int nr)
|
|
{
|
|
int i;
|
|
for (i = 0; i < nr; i++)
|
|
refspec_append(rs, refspecs[i]);
|
|
}
|
|
|
|
void refspec_clear(struct refspec *rs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < rs->nr; i++)
|
|
refspec_item_clear(&rs->items[i]);
|
|
|
|
FREE_AND_NULL(rs->items);
|
|
rs->alloc = 0;
|
|
rs->nr = 0;
|
|
|
|
rs->fetch = 0;
|
|
}
|
|
|
|
int valid_fetch_refspec(const char *fetch_refspec_str)
|
|
{
|
|
struct refspec_item refspec;
|
|
int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str);
|
|
refspec_item_clear(&refspec);
|
|
return ret;
|
|
}
|
|
|
|
void refspec_ref_prefixes(const struct refspec *rs,
|
|
struct strvec *ref_prefixes)
|
|
{
|
|
int i;
|
|
for (i = 0; i < rs->nr; i++) {
|
|
const struct refspec_item *item = &rs->items[i];
|
|
const char *prefix = NULL;
|
|
|
|
if (item->negative)
|
|
continue;
|
|
|
|
if (rs->fetch) {
|
|
if (item->exact_sha1)
|
|
continue;
|
|
prefix = item->src;
|
|
} else {
|
|
/*
|
|
* Pushes can have an explicit destination like
|
|
* "foo:bar", or can implicitly use the src for both
|
|
* ("foo" is the same as "foo:foo").
|
|
*/
|
|
if (item->dst)
|
|
prefix = item->dst;
|
|
else if (item->src && !item->exact_sha1)
|
|
prefix = item->src;
|
|
}
|
|
|
|
if (!prefix)
|
|
continue;
|
|
|
|
if (item->pattern) {
|
|
const char *glob = strchr(prefix, '*');
|
|
strvec_pushf(ref_prefixes, "%.*s",
|
|
(int)(glob - prefix),
|
|
prefix);
|
|
} else {
|
|
expand_ref_prefix(ref_prefixes, prefix);
|
|
}
|
|
}
|
|
}
|
|
|
|
int match_refname_with_pattern(const char *pattern, const char *refname,
|
|
const char *replacement, char **result)
|
|
{
|
|
const char *kstar = strchr(pattern, '*');
|
|
size_t klen;
|
|
size_t ksuffixlen;
|
|
size_t namelen;
|
|
int ret;
|
|
if (!kstar)
|
|
die(_("pattern '%s' has no '*'"), pattern);
|
|
klen = kstar - pattern;
|
|
ksuffixlen = strlen(kstar + 1);
|
|
namelen = strlen(refname);
|
|
ret = !strncmp(refname, pattern, klen) && namelen >= klen + ksuffixlen &&
|
|
!memcmp(refname + namelen - ksuffixlen, kstar + 1, ksuffixlen);
|
|
if (ret && replacement) {
|
|
struct strbuf sb = STRBUF_INIT;
|
|
const char *vstar = strchr(replacement, '*');
|
|
if (!vstar)
|
|
die(_("replacement '%s' has no '*'"), replacement);
|
|
strbuf_add(&sb, replacement, vstar - replacement);
|
|
strbuf_add(&sb, refname + klen, namelen - klen - ksuffixlen);
|
|
strbuf_addstr(&sb, vstar + 1);
|
|
*result = strbuf_detach(&sb, NULL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int refspec_match(const struct refspec_item *refspec,
|
|
const char *name)
|
|
{
|
|
if (refspec->pattern)
|
|
return match_refname_with_pattern(refspec->src, name, NULL, NULL);
|
|
|
|
return !strcmp(refspec->src, name);
|
|
}
|
|
|
|
int refname_matches_negative_refspec_item(const char *refname, struct refspec *rs)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
if (rs->items[i].negative && refspec_match(&rs->items[i], refname))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int refspec_find_negative_match(struct refspec *rs, struct refspec_item *query)
|
|
{
|
|
int i, matched_negative = 0;
|
|
int find_src = !query->src;
|
|
struct string_list reversed = STRING_LIST_INIT_DUP;
|
|
const char *needle = find_src ? query->dst : query->src;
|
|
|
|
/*
|
|
* Check whether the queried ref matches any negative refpsec. If so,
|
|
* then we should ultimately treat this as not matching the query at
|
|
* all.
|
|
*
|
|
* Note that negative refspecs always match the source, but the query
|
|
* item uses the destination. To handle this, we apply pattern
|
|
* refspecs in reverse to figure out if the query source matches any
|
|
* of the negative refspecs.
|
|
*
|
|
* The first loop finds and expands all positive refspecs
|
|
* matched by the queried ref.
|
|
*
|
|
* The second loop checks if any of the results of the first loop
|
|
* match any negative refspec.
|
|
*/
|
|
for (i = 0; i < rs->nr; i++) {
|
|
struct refspec_item *refspec = &rs->items[i];
|
|
char *expn_name;
|
|
|
|
if (refspec->negative)
|
|
continue;
|
|
|
|
/* Note the reversal of src and dst */
|
|
if (refspec->pattern) {
|
|
const char *key = refspec->dst ? refspec->dst : refspec->src;
|
|
const char *value = refspec->src;
|
|
|
|
if (match_refname_with_pattern(key, needle, value, &expn_name))
|
|
string_list_append_nodup(&reversed, expn_name);
|
|
} else if (refspec->matching) {
|
|
/* For the special matching refspec, any query should match */
|
|
string_list_append(&reversed, needle);
|
|
} else if (!refspec->src) {
|
|
BUG("refspec->src should not be null here");
|
|
} else if (!strcmp(needle, refspec->src)) {
|
|
string_list_append(&reversed, refspec->src);
|
|
}
|
|
}
|
|
|
|
for (i = 0; !matched_negative && i < reversed.nr; i++) {
|
|
if (refname_matches_negative_refspec_item(reversed.items[i].string, rs))
|
|
matched_negative = 1;
|
|
}
|
|
|
|
string_list_clear(&reversed, 0);
|
|
|
|
return matched_negative;
|
|
}
|
|
|
|
void refspec_find_all_matches(struct refspec *rs,
|
|
struct refspec_item *query,
|
|
struct string_list *results)
|
|
{
|
|
int i;
|
|
int find_src = !query->src;
|
|
|
|
if (find_src && !query->dst)
|
|
BUG("refspec_find_all_matches: need either src or dst");
|
|
|
|
if (refspec_find_negative_match(rs, query))
|
|
return;
|
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
struct refspec_item *refspec = &rs->items[i];
|
|
const char *key = find_src ? refspec->dst : refspec->src;
|
|
const char *value = find_src ? refspec->src : refspec->dst;
|
|
const char *needle = find_src ? query->dst : query->src;
|
|
char **result = find_src ? &query->src : &query->dst;
|
|
|
|
if (!refspec->dst || refspec->negative)
|
|
continue;
|
|
if (refspec->pattern) {
|
|
if (match_refname_with_pattern(key, needle, value, result))
|
|
string_list_append_nodup(results, *result);
|
|
} else if (!strcmp(needle, key)) {
|
|
string_list_append(results, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
int refspec_find_match(struct refspec *rs, struct refspec_item *query)
|
|
{
|
|
int i;
|
|
int find_src = !query->src;
|
|
const char *needle = find_src ? query->dst : query->src;
|
|
char **result = find_src ? &query->src : &query->dst;
|
|
|
|
if (find_src && !query->dst)
|
|
BUG("refspec_find_match: need either src or dst");
|
|
|
|
if (refspec_find_negative_match(rs, query))
|
|
return -1;
|
|
|
|
for (i = 0; i < rs->nr; i++) {
|
|
struct refspec_item *refspec = &rs->items[i];
|
|
const char *key = find_src ? refspec->dst : refspec->src;
|
|
const char *value = find_src ? refspec->src : refspec->dst;
|
|
|
|
if (!refspec->dst || refspec->negative)
|
|
continue;
|
|
if (refspec->pattern) {
|
|
if (match_refname_with_pattern(key, needle, value, result)) {
|
|
query->force = refspec->force;
|
|
return 0;
|
|
}
|
|
} else if (!strcmp(needle, key)) {
|
|
*result = xstrdup(value);
|
|
query->force = refspec->force;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
struct ref *apply_negative_refspecs(struct ref *ref_map, struct refspec *rs)
|
|
{
|
|
struct ref **tail;
|
|
|
|
for (tail = &ref_map; *tail; ) {
|
|
struct ref *ref = *tail;
|
|
|
|
if (refname_matches_negative_refspec_item(ref->name, rs)) {
|
|
*tail = ref->next;
|
|
free(ref->peer_ref);
|
|
free(ref);
|
|
} else
|
|
tail = &ref->next;
|
|
}
|
|
|
|
return ref_map;
|
|
}
|
|
|
|
char *apply_refspecs(struct refspec *rs, const char *name)
|
|
{
|
|
struct refspec_item query;
|
|
|
|
memset(&query, 0, sizeof(struct refspec_item));
|
|
query.src = (char *)name;
|
|
|
|
if (refspec_find_match(rs, &query))
|
|
return NULL;
|
|
|
|
return query.dst;
|
|
}
|