diff options
Diffstat (limited to 'setup.c')
-rw-r--r-- | setup.c | 202 |
1 files changed, 169 insertions, 33 deletions
@@ -1,10 +1,11 @@ #include "cache.h" #include "dir.h" +#include "string-list.h" static int inside_git_dir = -1; static int inside_work_tree = -1; -char *prefix_path(const char *prefix, int len, const char *path) +static char *prefix_path_gently(const char *prefix, int len, const char *path) { const char *orig = path; char *sanitized; @@ -31,7 +32,8 @@ char *prefix_path(const char *prefix, int len, const char *path) if (strncmp(sanitized, work_tree, len) || (len > root_len && sanitized[len] != '\0' && sanitized[len] != '/')) { error_out: - die("'%s' is outside repository", orig); + free(sanitized); + return NULL; } if (sanitized[len] == '/') len++; @@ -40,12 +42,38 @@ char *prefix_path(const char *prefix, int len, const char *path) return sanitized; } +char *prefix_path(const char *prefix, int len, const char *path) +{ + char *r = prefix_path_gently(prefix, len, path); + if (!r) + die("'%s' is outside repository", path); + return r; +} + +int path_inside_repo(const char *prefix, const char *path) +{ + int len = prefix ? strlen(prefix) : 0; + char *r = prefix_path_gently(prefix, len, path); + if (r) { + free(r); + return 1; + } + return 0; +} + int check_filename(const char *prefix, const char *arg) { const char *name; struct stat st; - name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!prefixcmp(arg, ":/")) { + if (arg[2] == '\0') /* ":/" is root dir, always exists */ + return 1; + name = arg + 2; + } 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) @@ -53,24 +81,27 @@ int check_filename(const char *prefix, const char *arg) die_errno("failed to stat '%s'", arg); } -static void NORETURN die_verify_filename(const char *prefix, const char *arg) +static void NORETURN die_verify_filename(const char *prefix, + const char *arg, + int diagnose_misspelt_rev) { - unsigned char sha1[20]; - unsigned mode; - + if (!diagnose_misspelt_rev) + die("%s: no such path in the working tree.\n" + "Use 'git <command> -- <path>...' to specify paths that do not exist locally.", + arg); /* * Saying "'(icase)foo' does not exist in the index" when the * user gave us ":(icase)foo" is just stupid. A magic pathspec * begins with a colon and is followed by a non-alnum; do not - * let get_sha1_with_mode_1(only_to_die=1) to even trigger. + * let maybe_die_on_misspelt_object_name() even trigger. */ if (!(arg[0] == ':' && !isalnum(arg[1]))) - /* try a detailed diagnostic ... */ - get_sha1_with_mode_1(arg, sha1, &mode, 1, prefix); + maybe_die_on_misspelt_object_name(arg, prefix); /* ... or fall back the most general message. */ die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" - "Use '--' to separate paths from revisions", arg); + "Use '--' to separate paths from revisions, like this:\n" + "'git <command> [<revision>...] -- [<file>...]'", arg); } @@ -80,14 +111,29 @@ static void NORETURN die_verify_filename(const char *prefix, const char *arg) * 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) +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)) return; - die_verify_filename(prefix, arg); + die_verify_filename(prefix, arg, diagnose_misspelt_rev); } /* @@ -104,7 +150,8 @@ void verify_non_filename(const char *prefix, const char *arg) if (!check_filename(prefix, arg)) return; die("ambiguous argument '%s': both revision and filename\n" - "Use '--' to separate filenames from revisions", arg); + "Use '--' to separate paths from revisions, like this:\n" + "'git <command> [<revision>...] -- [<file>...]'", arg); } /* @@ -160,10 +207,11 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *copyfrom && *copyfrom != ')'; copyfrom = nextat) { size_t len = strcspn(copyfrom, ",)"); - if (copyfrom[len] == ')') - nextat = copyfrom + len; - else + 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++) @@ -176,8 +224,9 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char die("Invalid pathspec magic '%.*s' in '%s'", (int) len, copyfrom, elt); } - if (*copyfrom == ')') - copyfrom++; + if (*copyfrom != ')') + die("Missing ')' at the end of pathspec magic in '%s'", elt); + copyfrom++; } else { /* shorthand */ for (copyfrom = elt + 1; @@ -206,6 +255,25 @@ static const char *prefix_pathspec(const char *prefix, int prefixlen, const char return prefix_path(prefix, prefixlen, copyfrom); } +/* + * N.B. get_pathspec() is deprecated in favor of the "struct pathspec" + * based interface - see pathspec_magic above. + * + * Arguments: + * - prefix - a path relative to the root of the working tree + * - pathspec - a list of paths underneath the prefix path + * + * Iterates over pathspec, prepending each path with prefix, + * and return the resulting list. + * + * If pathspec is empty, return a singleton list containing prefix. + * + * If pathspec and prefix are both empty, return an empty list. + * + * This is typically used by built-in commands such as add.c, in order + * to normalize argv arguments provided to the built-in into a list of + * paths to process, all relative to the root of the working tree. + */ const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; @@ -247,7 +315,7 @@ const char **get_pathspec(const char *prefix, const char **pathspec) * a proper "ref:", or a regular file HEAD that has a properly * formatted sha1 object name. */ -static int is_git_directory(const char *suspect) +int is_git_directory(const char *suspect) { char path[PATH_MAX]; size_t len = strlen(suspect); @@ -292,6 +360,7 @@ int is_inside_work_tree(void) void setup_work_tree(void) { + struct strbuf sb = STRBUF_INIT; const char *work_tree, *git_dir; static int initialized = 0; @@ -311,8 +380,10 @@ void setup_work_tree(void) if (getenv(GIT_WORK_TREE_ENVIRONMENT)) setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1); - set_git_dir(relative_path(git_dir, work_tree)); + set_git_dir(relative_path(git_dir, work_tree, &sb)); initialized = 1; + + strbuf_release(&sb); } static int check_repository_format_gently(const char *gitdir, int *nongit_ok) @@ -457,6 +528,12 @@ static const char *setup_explicit_git_dir(const char *gitdirenv, set_git_work_tree(core_worktree); } } + else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) { + /* #16d */ + set_git_dir(gitdirenv); + free(gitfile); + return NULL; + } else /* #2, #10 */ set_git_work_tree("."); @@ -535,6 +612,8 @@ static const char *setup_bare_git_dir(char *cwd, int offset, int len, int *nongi if (check_repository_format_gently(".", nongit_ok)) return NULL; + setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1); + /* --work-tree is set without --git-dir; use discovered one */ if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) { const char *gitdir; @@ -569,27 +648,61 @@ static const char *setup_nongit(const char *cwd, int *nongit_ok) return NULL; } -static dev_t get_device_or_die(const char *path, const char *prefix) +static dev_t get_device_or_die(const char *path, const char *prefix, int prefix_len) { struct stat buf; - if (stat(path, &buf)) - die_errno("failed to stat '%s%s%s'", + if (stat(path, &buf)) { + die_errno("failed to stat '%*s%s%s'", + prefix_len, prefix ? prefix : "", prefix ? "/" : "", path); + } return buf.st_dev; } /* + * A "string_list_each_func_t" function that canonicalizes an entry + * from GIT_CEILING_DIRECTORIES using real_path_if_valid(), or + * discards it if unusable. The presence of an empty entry in + * GIT_CEILING_DIRECTORIES turns off canonicalization for all + * subsequent entries. + */ +static int canonicalize_ceiling_entry(struct string_list_item *item, + void *cb_data) +{ + int *empty_entry_found = cb_data; + char *ceil = item->string; + + if (!*ceil) { + *empty_entry_found = 1; + return 0; + } else if (!is_absolute_path(ceil)) { + return 0; + } else if (*empty_entry_found) { + /* Keep entry but do not canonicalize it */ + return 1; + } else { + const char *real_path = real_path_if_valid(ceil); + if (!real_path) + return 0; + free(item->string); + item->string = xstrdup(real_path); + return 1; + } +} + +/* * We cannot decide in this function whether we are in the work tree or * not, since the config can only be read _after_ this function was called. */ static const char *setup_git_directory_gently_1(int *nongit_ok) { const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); + struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; static char cwd[PATH_MAX+1]; const char *gitdirenv, *ret; char *gitfile; - int len, offset, ceil_offset; + int len, offset, offset_parent, ceil_offset = -1; dev_t current_device = 0; int one_filesystem = 1; @@ -614,7 +727,16 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) if (gitdirenv) return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok); - ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs); + if (env_ceiling_dirs) { + int empty_entry_found = 0; + + string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1); + filter_string_list(&ceiling_dirs, 0, + canonicalize_ceiling_entry, &empty_entry_found); + ceil_offset = longest_ancestor_length(cwd, &ceiling_dirs); + string_list_clear(&ceiling_dirs, 0); + } + if (ceil_offset < 0 && has_dos_drive_prefix(cwd)) ceil_offset = 1; @@ -631,7 +753,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); if (one_filesystem) - current_device = get_device_or_die(".", NULL); + current_device = get_device_or_die(".", NULL, 0); for (;;) { gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile) @@ -653,11 +775,12 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) if (is_git_directory(".")) return setup_bare_git_dir(cwd, offset, len, nongit_ok); - while (--offset > ceil_offset && cwd[offset] != '/'); - if (offset <= ceil_offset) + offset_parent = offset; + while (--offset_parent > ceil_offset && cwd[offset_parent] != '/'); + if (offset_parent <= ceil_offset) return setup_nongit(cwd, nongit_ok); if (one_filesystem) { - dev_t parent_device = get_device_or_die("..", cwd); + dev_t parent_device = get_device_or_die("..", cwd, offset); if (parent_device != current_device) { if (nongit_ok) { if (chdir(cwd)) @@ -666,7 +789,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) return NULL; } cwd[offset] = '\0'; - die("Not a git repository (or any parent up to mount parent %s)\n" + die("Not a git repository (or any parent up to mount point %s)\n" "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd); } } @@ -674,6 +797,7 @@ static const char *setup_git_directory_gently_1(int *nongit_ok) cwd[offset] = '\0'; die_errno("Cannot change to '%s/..'", cwd); } + offset = offset_parent; } } @@ -683,9 +807,9 @@ const char *setup_git_directory_gently(int *nongit_ok) prefix = setup_git_directory_gently_1(nongit_ok); if (prefix) - setenv("GIT_PREFIX", prefix, 1); + setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1); else - setenv("GIT_PREFIX", "", 1); + setenv(GIT_PREFIX_ENVIRONMENT, "", 1); if (startup_info) { startup_info->have_repository = !nongit_ok || !*nongit_ok; @@ -787,3 +911,15 @@ const char *resolve_gitdir(const char *suspect) return suspect; return read_gitfile(suspect); } + +/* if any standard file descriptor is missing open it to /dev/null */ +void sanitize_stdfds(void) +{ + int fd = open("/dev/null", O_RDWR, 0); + while (fd != -1 && fd < 2) + fd = dup(fd); + if (fd == -1) + die_errno("open /dev/null or dup failed"); + if (fd > 2) + close(fd); +} |