int init_pathspec(struct pathspec *pathspec, const char **paths) { const char **p = paths; int i; memset(pathspec, 0, sizeof(*pathspec)); if (!p) return 0; while (*p) p++; pathspec->raw = paths; pathspec->nr = p - paths; if (!pathspec->nr) return 0; pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); for (i = 0; i < pathspec->nr; i++) { struct pathspec_item *item = pathspec->items+i; const char *path = paths[i]; item->match = path; item->len = strlen(path); item->use_wildcard = !no_wildcard(path); if (item->use_wildcard) pathspec->has_wildcard = 1; } qsort(pathspec->items, pathspec->nr, sizeof(struct pathspec_item), pathspec_item_cmp); return 0; }
/* * Verify a filename that we got as an argument for a pathspec * entry. Note that a filename that begins with "-" never verifies * as true, because even if such a filename were to exist, we want * it to be preceded by the "--" marker (or we want the user to * use a format like "./-filename") * * The "diagnose_misspelt_rev" is used to provide a user-friendly * diagnosis when dying upon finding that "name" is not a pathname. * If set to 1, the diagnosis will try to diagnose "name" as an * invalid object name (e.g. HEAD:foo). If set to 0, the diagnosis * will only complain about an inexisting file. * * This function is typically called to check that a "file or rev" * argument is unambiguous. In this case, the caller will want * diagnose_misspelt_rev == 1 when verifying the first non-rev * argument (which could have been a revision), and * diagnose_misspelt_rev == 0 for the next ones (because we already * saw a filename, there's not ambiguity anymore). */ void verify_filename(const char *prefix, const char *arg, int diagnose_misspelt_rev) { if (*arg == '-') die("bad flag '%s' used after filename", arg); if (check_filename(prefix, arg) || !no_wildcard(arg)) return; die_verify_filename(prefix, arg, diagnose_misspelt_rev); }
void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { struct exclude *x; size_t len; int to_exclude = 1; int flags = 0; if (*string == '!') { to_exclude = 0; string++; } len = strlen(string); if (len && string[len - 1] == '/') { char *s; x = xmalloc(sizeof(*x) + len); s = (char *)(x+1); memcpy(s, string, len - 1); s[len - 1] = '\0'; string = s; x->pattern = s; flags = EXC_FLAG_MUSTBEDIR; } else { x = xmalloc(sizeof(*x)); x->pattern = string; } x->to_exclude = to_exclude; x->patternlen = strlen(string); x->base = base; x->baselen = baselen; x->flags = flags; if (!strchr(string, '/')) x->flags |= EXC_FLAG_NODIR; if (no_wildcard(string)) x->flags |= EXC_FLAG_NOWILDCARD; if (*string == '*' && no_wildcard(string+1)) x->flags |= EXC_FLAG_ENDSWITH; ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); which->excludes[which->nr++] = x; }
int init_pathspec(struct pathspec *pathspec, const char **paths) { const char **p = paths; int i; memset(pathspec, 0, sizeof(*pathspec)); if (!p) return 0; while (*p) p++; pathspec->raw = paths; pathspec->nr = p - paths; if (!pathspec->nr) return 0; pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr); for (i = 0; i < pathspec->nr; i++) { struct pathspec_item *item = pathspec->items+i; const char *path = paths[i]; item->match = path; item->len = strlen(path); item->flags = 0; if (limit_pathspec_to_literal()) { item->nowildcard_len = item->len; } else { item->nowildcard_len = simple_length(path); if (item->nowildcard_len < item->len) { pathspec->has_wildcard = 1; if (path[item->nowildcard_len] == '*' && no_wildcard(path + item->nowildcard_len + 1)) item->flags |= PATHSPEC_ONESTAR; } } } qsort(pathspec->items, pathspec->nr, sizeof(struct pathspec_item), pathspec_item_cmp); return 0; }
int check_filename(const char *prefix, const char *arg) { const char *name; struct stat st; if (starts_with(arg, ":/")) { if (arg[2] == '\0') /* ":/" is root dir, always exists */ return 1; name = arg + 2; } else if (!no_wildcard(arg)) return 1; else if (prefix) name = prefix_filename(prefix, strlen(prefix), arg); else name = arg; if (!lstat(name, &st)) return 1; /* file exists */ if (errno == ENOENT || errno == ENOTDIR) return 0; /* file does not exist */ die_errno("failed to stat '%s'", arg); }
void parse_exclude_pattern(const char **pattern, int *patternlen, int *flags, int *nowildcardlen) { const char *p = *pattern; size_t i, len; *flags = 0; if (*p == '!') { *flags |= EXC_FLAG_NEGATIVE; p++; } len = strlen(p); if (len && p[len - 1] == '/') { len--; *flags |= EXC_FLAG_MUSTBEDIR; } for (i = 0; i < len; i++) { if (p[i] == '/') break; } if (i == len) *flags |= EXC_FLAG_NODIR; *nowildcardlen = simple_length(p); /* * we should have excluded the trailing slash from 'p' too, * but that's one more allocation. Instead just make sure * nowildcardlen does not exceed real patternlen */ if (*nowildcardlen > len) *nowildcardlen = len; if (*p == '*' && no_wildcard(p + 1)) *flags |= EXC_FLAG_ENDSWITH; *pattern = p; *patternlen = len; }
static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev, int *dwim_remotes_matched) { struct tree **source_tree = &opts->source_tree; const char **new_branch = &opts->new_branch; int argcount = 0; struct object_id branch_rev; const char *arg; int dash_dash_pos; int has_dash_dash = 0; int i; /* * case 1: git checkout <ref> -- [<paths>] * * <ref> must be a valid tree, everything after the '--' must be * a path. * * case 2: git checkout -- [<paths>] * * everything after the '--' must be paths. * * case 3: git checkout <something> [--] * * (a) If <something> is a commit, that is to * switch to the branch or detach HEAD at it. As a special case, * if <something> is A...B (missing A or B means HEAD but you can * omit at most one side), and if there is a unique merge base * between A and B, A...B names that merge base. * * (b) If <something> is _not_ a commit, either "--" is present * or <something> is not a path, no -t or -b was given, and * and there is a tracking branch whose name is <something> * in one and only one remote (or if the branch exists on the * remote named in checkout.defaultRemote), then this is a * short-hand to fork local <something> from that * remote-tracking branch. * * (c) Otherwise, if "--" is present, treat it like case (1). * * (d) Otherwise : * - if it's a reference, treat it like case (1) * - else if it's a path, treat it like case (2) * - else: fail. * * case 4: git checkout <something> <paths> * * The first argument must not be ambiguous. * - If it's *only* a reference, treat it like case (1). * - If it's only a path, treat it like case (2). * - else: fail. * */ if (!argc) return 0; arg = argv[0]; dash_dash_pos = -1; for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "--")) { dash_dash_pos = i; break; } } if (dash_dash_pos == 0) return 1; /* case (2) */ else if (dash_dash_pos == 1) has_dash_dash = 1; /* case (3) or (1) */ else if (dash_dash_pos >= 2) die(_("only one reference expected, %d given."), dash_dash_pos); opts->count_checkout_paths = !opts->quiet && !has_dash_dash; if (!strcmp(arg, "-")) arg = "@{-1}"; if (get_oid_mb(arg, rev)) { /* * Either case (3) or (4), with <something> not being * a commit, or an attempt to use case (1) with an * invalid ref. * * It's likely an error, but we need to find out if * we should auto-create the branch, case (3).(b). */ int recover_with_dwim = dwim_new_local_branch_ok; int could_be_checkout_paths = !has_dash_dash && check_filename(opts->prefix, arg); if (!has_dash_dash && !no_wildcard(arg)) recover_with_dwim = 0; /* * Accept "git checkout foo" and "git checkout foo --" * as candidates for dwim. */ if (!(argc == 1 && !has_dash_dash) && !(argc == 2 && has_dash_dash)) recover_with_dwim = 0; if (recover_with_dwim) { const char *remote = unique_tracking_name(arg, rev, dwim_remotes_matched); if (remote) { if (could_be_checkout_paths) die(_("'%s' could be both a local file and a tracking branch.\n" "Please use -- (and optionally --no-guess) to disambiguate"), arg); *new_branch = arg; arg = remote; /* DWIMmed to create local branch, case (3).(b) */ } else { recover_with_dwim = 0; } } if (!recover_with_dwim) { if (has_dash_dash) die(_("invalid reference: %s"), arg); return argcount; } } /* we can't end up being in (2) anymore, eat the argument */ argcount++; argv++; argc--; new_branch_info->name = arg; setup_branch_path(new_branch_info); if (!check_refname_format(new_branch_info->path, 0) && !read_ref(new_branch_info->path, &branch_rev)) oidcpy(rev, &branch_rev); else new_branch_info->path = NULL; /* not an existing branch */ new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1); if (!new_branch_info->commit) { /* not a commit */ *source_tree = parse_tree_indirect(rev); } else { parse_commit_or_die(new_branch_info->commit); *source_tree = get_commit_tree(new_branch_info->commit); } if (!*source_tree) /* case (1): want a tree */ die(_("reference is not a tree: %s"), arg); if (!has_dash_dash) { /* case (3).(d) -> (1) */ /* * Do not complain the most common case * git checkout branch * even if there happen to be a file called 'branch'; * it would be extremely annoying. */ if (argc) verify_non_filename(opts->prefix, arg); } else { argcount++; argv++; argc--; } return argcount; }
/* * Take an element of a pathspec and check for magic signatures. * Append the result to the prefix. Return the magic bitmap. * * For now, we only parse the syntax and throw out anything other than * "top" magic. * * NEEDSWORK: This needs to be rewritten when we start migrating * get_pathspec() users to use the "struct pathspec" interface. For * example, a pathspec element may be marked as case-insensitive, but * the prefix part must always match literally, and a single stupid * string cannot express such a case. */ static unsigned prefix_pathspec(struct pathspec_item *item, unsigned *p_short_magic, const char **raw, unsigned flags, const char *prefix, int prefixlen, const char *elt) { static int literal_global = -1; static int glob_global = -1; static int noglob_global = -1; static int icase_global = -1; unsigned magic = 0, short_magic = 0, global_magic = 0; const char *copyfrom = elt, *long_magic_end = NULL; char *match; int i, pathspec_prefix = -1; if (literal_global < 0) literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0); if (literal_global) global_magic |= PATHSPEC_LITERAL; if (glob_global < 0) glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0); if (glob_global) global_magic |= PATHSPEC_GLOB; if (noglob_global < 0) noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0); if (glob_global && noglob_global) die(_("global 'glob' and 'noglob' pathspec settings are incompatible")); if (icase_global < 0) icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0); if (icase_global) global_magic |= PATHSPEC_ICASE; if ((global_magic & PATHSPEC_LITERAL) && (global_magic & ~PATHSPEC_LITERAL)) die(_("global 'literal' pathspec setting is incompatible " "with all other global pathspec settings")); if (flags & PATHSPEC_LITERAL_PATH) global_magic = 0; if (elt[0] != ':' || literal_global || (flags & PATHSPEC_LITERAL_PATH)) { ; /* nothing to do */ } else if (elt[1] == '(') { /* longhand */ const char *nextat; for (copyfrom = elt + 2; *copyfrom && *copyfrom != ')'; copyfrom = nextat) { size_t len = strcspn(copyfrom, ",)"); if (copyfrom[len] == ',') nextat = copyfrom + len + 1; else /* handle ')' and '\0' */ nextat = copyfrom + len; if (!len) continue; for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) { if (strlen(pathspec_magic[i].name) == len && !strncmp(pathspec_magic[i].name, copyfrom, len)) { magic |= pathspec_magic[i].bit; break; } if (starts_with(copyfrom, "prefix:")) { char *endptr; pathspec_prefix = strtol(copyfrom + 7, &endptr, 10); if (endptr - copyfrom != len) die(_("invalid parameter for pathspec magic 'prefix'")); /* "i" would be wrong, but it does not matter */ break; } } if (ARRAY_SIZE(pathspec_magic) <= i) die(_("Invalid pathspec magic '%.*s' in '%s'"), (int) len, copyfrom, elt); } if (*copyfrom != ')') die(_("Missing ')' at the end of pathspec magic in '%s'"), elt); long_magic_end = copyfrom; copyfrom++; } else { /* shorthand */ for (copyfrom = elt + 1; *copyfrom && *copyfrom != ':'; copyfrom++) { char ch = *copyfrom; if (!is_pathspec_magic(ch)) break; for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) if (pathspec_magic[i].mnemonic == ch) { short_magic |= pathspec_magic[i].bit; break; } if (ARRAY_SIZE(pathspec_magic) <= i) die(_("Unimplemented pathspec magic '%c' in '%s'"), ch, elt); } if (*copyfrom == ':') copyfrom++; } magic |= short_magic; *p_short_magic = short_magic; /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specified */ if (noglob_global && !(magic & PATHSPEC_GLOB)) global_magic |= PATHSPEC_LITERAL; /* --glob-pathspec is overridden by :(literal) */ if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL)) global_magic &= ~PATHSPEC_GLOB; magic |= global_magic; if (pathspec_prefix >= 0 && (prefixlen || (prefix && *prefix))) die("BUG: 'prefix' magic is supposed to be used at worktree's root"); if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB)) die(_("%s: 'literal' and 'glob' are incompatible"), elt); if (pathspec_prefix >= 0) { match = xstrdup(copyfrom); prefixlen = pathspec_prefix; } else if (magic & PATHSPEC_FROMTOP) { match = xstrdup(copyfrom); prefixlen = 0; } else { match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); if (!match) die(_("%s: '%s' is outside repository"), elt, copyfrom); } *raw = item->match = match; /* * Prefix the pathspec (keep all magic) and assign to * original. Useful for passing to another command. */ if (flags & PATHSPEC_PREFIX_ORIGIN) { struct strbuf sb = STRBUF_INIT; const char *start = elt; if (prefixlen && !literal_global) { /* Preserve the actual prefix length of each pattern */ if (short_magic) die("BUG: prefixing on short magic is not supported"); else if (long_magic_end) { strbuf_add(&sb, start, long_magic_end - start); strbuf_addf(&sb, ",prefix:%d", prefixlen); start = long_magic_end; } else { if (*start == ':') start++; strbuf_addf(&sb, ":(prefix:%d)", prefixlen); } } strbuf_add(&sb, start, copyfrom - start); strbuf_addstr(&sb, match); item->original = strbuf_detach(&sb, NULL); } else item->original = elt; item->len = strlen(item->match); item->prefix = prefixlen; if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) && (item->len >= 1 && item->match[item->len - 1] == '/') && (i = cache_name_pos(item->match, item->len - 1)) >= 0 && S_ISGITLINK(active_cache[i]->ce_mode)) { item->len--; match[item->len] = '\0'; } if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; int ce_len = ce_namelen(ce); if (!S_ISGITLINK(ce->ce_mode)) continue; if (item->len <= ce_len || match[ce_len] != '/' || memcmp(ce->name, match, ce_len)) continue; if (item->len == ce_len + 1) { /* strip trailing slash */ item->len--; match[item->len] = '\0'; } else die (_("Pathspec '%s' is in submodule '%.*s'"), elt, ce_len, ce->name); } if (magic & PATHSPEC_LITERAL) item->nowildcard_len = item->len; else { item->nowildcard_len = simple_length(item->match); if (item->nowildcard_len < prefixlen) item->nowildcard_len = prefixlen; } item->flags = 0; if (magic & PATHSPEC_GLOB) { /* * FIXME: should we enable ONESTAR in _GLOB for * pattern "* * / * . c"? */ } else { if (item->nowildcard_len < item->len && item->match[item->nowildcard_len] == '*' && no_wildcard(item->match + item->nowildcard_len + 1)) item->flags |= PATHSPEC_ONESTAR; } /* sanity checks, pathspec matchers assume these are sane */ assert(item->nowildcard_len <= item->len && item->prefix <= item->len); return magic; }