int url_is_local_not_ssh(const char *url) { const char *colon = strchr(url, ':'); const char *slash = strchr(url, '/'); return !colon || (slash && slash < colon) || has_dos_drive_prefix(url); }
/* * Give path as relative to prefix. * * The strbuf may or may not be used, so do not assume it contains the * returned path. */ const char *relative_path(const char *in, const char *prefix, struct strbuf *sb) { int in_len = in ? strlen(in) : 0; int prefix_len = prefix ? strlen(prefix) : 0; int in_off = 0; int prefix_off = 0; int i = 0, j = 0; if (!in_len) return "./"; else if (!prefix_len) return in; if (have_same_root(in, prefix)) /* bypass dos_drive, for "c:" is identical to "C:" */ i = j = has_dos_drive_prefix(in); else { return in; } while (i < prefix_len && j < in_len && prefix[i] == in[j]) { if (is_dir_sep(prefix[i])) { while (is_dir_sep(prefix[i])) i++; while (is_dir_sep(in[j])) j++; prefix_off = i; in_off = j; } else { i++; j++; } } if ( /* "prefix" seems like prefix of "in" */ i >= prefix_len && /* * but "/foo" is not a prefix of "/foobar" * (i.e. prefix not end with '/') */ prefix_off < prefix_len) { if (j >= in_len) { /* in="/a/b", prefix="/a/b" */ in_off = in_len; } else if (is_dir_sep(in[j])) { /* in="/a/b/c", prefix="/a/b" */ while (is_dir_sep(in[j])) j++; in_off = j; } else { /* in="/a/bbb/c", prefix="/a/b" */ i = prefix_off; } } else if ( /* "in" is short than "prefix" */ j >= in_len && /* "in" not end with '/' */ in_off < in_len) { if (is_dir_sep(prefix[i])) { /* in="/a/b", prefix="/a/b/c/" */ while (is_dir_sep(prefix[i])) i++; in_off = in_len; } } in += in_off; in_len -= in_off; if (i >= prefix_len) { if (!in_len) return "./"; else return in; } strbuf_reset(sb); strbuf_grow(sb, in_len); while (i < prefix_len) { if (is_dir_sep(prefix[i])) { strbuf_addstr(sb, "../"); while (is_dir_sep(prefix[i])) i++; continue; } i++; } if (!is_dir_sep(prefix[prefix_len - 1])) strbuf_addstr(sb, "../"); strbuf_addstr(sb, in); return sb->buf; }
/* * It is okay if dst == src, but they should not overlap otherwise. * * Performs the following normalizations on src, storing the result in dst: * - Ensures that components are separated by '/' (Windows only) * - Squashes sequences of '/'. * - Removes "." components. * - Removes ".." components, and the components the precede them. * Returns failure (non-zero) if a ".." component appears as first path * component anytime during the normalization. Otherwise, returns success (0). * * Note that this function is purely textual. It does not follow symlinks, * verify the existence of the path, or make any system calls. * * prefix_len != NULL is for a specific case of prefix_pathspec(): * assume that src == dst and src[0..prefix_len-1] is already * normalized, any time "../" eats up to the prefix_len part, * prefix_len is reduced. In the end prefix_len is the remaining * prefix that has not been overridden by user pathspec. * * NEEDSWORK: This function doesn't perform normalization w.r.t. trailing '/'. * For everything but the root folder itself, the normalized path should not * end with a '/', then the callers need to be fixed up accordingly. * */ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; int i = has_unc_prefix(src); for (i = i ? i : has_dos_drive_prefix(src); i > 0; i--) *dst++ = *src++; dst0 = dst; if (is_dir_sep(*src)) { *dst++ = '/'; while (is_dir_sep(*src)) src++; } for (;;) { char c = *src; /* * A path component that begins with . could be * special: * (1) "." and ends -- ignore and terminate. * (2) "./" -- ignore them, eat slash and continue. * (3) ".." and ends -- strip one and terminate. * (4) "../" -- strip one, eat slash and continue. */ if (c == '.') { if (!src[1]) { /* (1) */ src++; } else if (is_dir_sep(src[1])) { /* (2) */ src += 2; while (is_dir_sep(*src)) src++; continue; } else if (src[1] == '.') { if (!src[2]) { /* (3) */ src += 2; goto up_one; } else if (is_dir_sep(src[2])) { /* (4) */ src += 3; while (is_dir_sep(*src)) src++; goto up_one; } } } /* copy up to the next '/', and eat all '/' */ while ((c = *src++) != '\0' && !is_dir_sep(c)) *dst++ = c; if (is_dir_sep(c)) { *dst++ = '/'; while (is_dir_sep(c)) c = *src++; src--; } else if (!c) break; continue; up_one: /* * dst0..dst is prefix portion, and dst[-1] is '/'; * go up one level. */ dst--; /* go to trailing '/' */ if (dst <= dst0) return -1; /* Windows: dst[-1] cannot be backslash anymore */ while (dst0 < dst && dst[-1] != '/') dst--; if (prefix_len && *prefix_len > dst - dst0) *prefix_len = dst - dst0; } *dst = '\0'; return 0; }
/* * 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. */ const char *setup_git_directory_gently(int *nongit_ok) { const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT); const char *env_ceiling_dirs = getenv(CEILING_DIRECTORIES_ENVIRONMENT); static char cwd[PATH_MAX+1]; const char *gitdirenv; const char *gitfile_dir; int len, offset, ceil_offset; /* * Let's assume that we are in a git repository. * If it turns out later that we are somewhere else, the value will be * updated accordingly. */ if (nongit_ok) *nongit_ok = 0; /* * If GIT_DIR is set explicitly, we're not going * to do any discovery, but we still do repository * validation. */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); if (gitdirenv) { if (PATH_MAX - 40 < strlen(gitdirenv)) die("'$%s' too big", GIT_DIR_ENVIRONMENT); if (is_git_directory(gitdirenv)) { static char buffer[1024 + 1]; const char *retval; if (!work_tree_env) { retval = set_work_tree(gitdirenv); /* config may override worktree */ if (check_repository_format_gently(nongit_ok)) return NULL; return retval; } if (check_repository_format_gently(nongit_ok)) return NULL; retval = get_relative_cwd(buffer, sizeof(buffer) - 1, get_git_work_tree()); if (!retval || !*retval) return NULL; set_git_dir(make_absolute_path(gitdirenv)); if (chdir(work_tree_env) < 0) die_errno ("Could not chdir to '%s'", work_tree_env); strcat(buffer, "/"); return retval; } if (nongit_ok) { *nongit_ok = 1; return NULL; } die("Not a git repository: '%s'", gitdirenv); } if (!getcwd(cwd, sizeof(cwd)-1)) die_errno("Unable to read current working directory"); ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs); if (ceil_offset < 0 && has_dos_drive_prefix(cwd)) ceil_offset = 1; /* * Test in the following order (relative to the cwd): * - .git (file containing "gitdir: <path>") * - .git/ * - ./ (bare) * - ../.git * - ../.git/ * - ../ (bare) * - ../../.git/ * etc. */ offset = len = strlen(cwd); for (;;) { gitfile_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile_dir) { if (set_git_dir(gitfile_dir)) die("Repository setup failed"); break; } if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) break; if (is_git_directory(".")) { inside_git_dir = 1; if (!work_tree_env) inside_work_tree = 0; if (offset != len) { cwd[offset] = '\0'; setenv(GIT_DIR_ENVIRONMENT, cwd, 1); } else setenv(GIT_DIR_ENVIRONMENT, ".", 1); check_repository_format_gently(nongit_ok); return NULL; } while (--offset > ceil_offset && cwd[offset] != '/'); if (offset <= ceil_offset) { if (nongit_ok) { if (chdir(cwd)) die_errno("Cannot come back to cwd"); *nongit_ok = 1; return NULL; } die("Not a git repository (or any of the parent directories): %s", DEFAULT_GIT_DIR_ENVIRONMENT); } if (chdir("..")) die_errno("Cannot change to '%s/..'", cwd); } inside_git_dir = 0; if (!work_tree_env) inside_work_tree = 1; git_work_tree_cfg = xstrndup(cwd, offset); if (check_repository_format_gently(nongit_ok)) return NULL; if (offset == len) return NULL; /* Make "offset" point to past the '/', and add a '/' at the end */ offset++; cwd[len++] = '/'; cwd[len] = 0; return cwd + offset; }
/* * 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 struct strbuf cwd = STRBUF_INIT; const char *gitdirenv, *ret; char *gitfile; int offset, offset_parent, ceil_offset = -1; dev_t current_device = 0; int one_filesystem = 1; /* * We may have read an incomplete configuration before * setting-up the git directory. If so, clear the cache so * that the next queries to the configuration reload complete * configuration (including the per-repo config file that we * ignored previously). */ git_config_clear(); /* * Let's assume that we are in a git repository. * If it turns out later that we are somewhere else, the value will be * updated accordingly. */ if (nongit_ok) *nongit_ok = 0; if (strbuf_getcwd(&cwd)) die_errno("Unable to read current working directory"); offset = cwd.len; /* * If GIT_DIR is set explicitly, we're not going * to do any discovery, but we still do repository * validation. */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); if (gitdirenv) return setup_explicit_git_dir(gitdirenv, &cwd, nongit_ok); 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.buf, &ceiling_dirs); string_list_clear(&ceiling_dirs, 0); } if (ceil_offset < 0 && has_dos_drive_prefix(cwd.buf)) ceil_offset = 1; /* * Test in the following order (relative to the cwd): * - .git (file containing "gitdir: <path>") * - .git/ * - ./ (bare) * - ../.git * - ../.git/ * - ../ (bare) * - ../../.git/ * etc. */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); if (one_filesystem) current_device = get_device_or_die(".", NULL, 0); for (;;) { gitfile = (char*)read_gitfile(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile) gitdirenv = gitfile = xstrdup(gitfile); else { if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; } if (gitdirenv) { ret = setup_discovered_git_dir(gitdirenv, &cwd, offset, nongit_ok); free(gitfile); return ret; } free(gitfile); if (is_git_directory(".")) return setup_bare_git_dir(&cwd, offset, nongit_ok); offset_parent = offset; while (--offset_parent > ceil_offset && cwd.buf[offset_parent] != '/'); if (offset_parent <= ceil_offset) return setup_nongit(cwd.buf, nongit_ok); if (one_filesystem) { dev_t parent_device = get_device_or_die("..", cwd.buf, offset); if (parent_device != current_device) { if (nongit_ok) { if (chdir(cwd.buf)) die_errno("Cannot come back to cwd"); *nongit_ok = 1; return NULL; } strbuf_setlen(&cwd, offset); 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.buf); } } if (chdir("..")) { strbuf_setlen(&cwd, offset); die_errno("Cannot change to '%s/..'", cwd.buf); } offset = offset_parent; } }
/* * This returns a dummy child_process if the transport protocol does not * need fork(2), or a struct child_process object if it does. Once done, * finish the connection with finish_connect() with the value returned from * this function (it is safe to call finish_connect() with NULL to support * the former case). * * If it returns, the connect is successful; it just dies on errors (this * will hopefully be changed in a libification effort, to return NULL when * the connection failed). */ struct child_process *git_connect(int fd[2], const char *url_orig, const char *prog, int flags) { char *url = xstrdup(url_orig); char *host, *path; char *end; int c; struct child_process *conn; enum protocol protocol = PROTO_LOCAL; int free_path = 0; char *port = NULL; const char **arg; struct strbuf cmd; /* Without this we cannot rely on waitpid() to tell * what happened to our children. */ signal(SIGCHLD, SIG_DFL); host = strstr(url, "://"); if (host) { *host = '\0'; protocol = get_protocol(url); host += 3; c = '/'; } else { host = url; c = ':'; } /* * Don't do destructive transforms with git:// as that * protocol code does '[]' unwrapping of its own. */ if (host[0] == '[') { end = strchr(host + 1, ']'); if (end) { if (protocol != PROTO_GIT) { *end = 0; host++; } end++; } else end = host; } else end = host; path = strchr(end, c); if (path && !has_dos_drive_prefix(end)) { if (c == ':') { protocol = PROTO_SSH; *path++ = '\0'; } } else path = end; if (!path || !*path) die("No path specified. See 'man git-pull' for valid url syntax"); /* * null-terminate hostname and point path to ~ for URL's like this: * ssh://host.xz/~user/repo */ if (protocol != PROTO_LOCAL && host != url) { char *ptr = path; if (path[1] == '~') path++; else { path = xstrdup(ptr); free_path = 1; } *ptr = '\0'; } /* * Add support for ssh port: ssh://host.xy:<port>/... */ if (protocol == PROTO_SSH && host != url) port = get_port(host); if (protocol == PROTO_GIT) { /* These underlying connection commands die() if they * cannot connect. */ char *target_host = xstrdup(host); if (git_use_proxy(host)) git_proxy_connect(fd, host); else git_tcp_connect(fd, host, flags); /* * Separate original protocol components prog and path * from extended host header with a NUL byte. * * Note: Do not add any other headers here! Doing so * will cause older git-daemon servers to crash. */ packet_write(fd[1], "%s %s%chost=%s%c", prog, path, 0, target_host, 0); free(target_host); free(url); if (free_path) free(path); return &no_fork; } conn = xcalloc(1, sizeof(*conn)); strbuf_init(&cmd, MAX_CMD_LEN); strbuf_addstr(&cmd, prog); strbuf_addch(&cmd, ' '); sq_quote_buf(&cmd, path); if (cmd.len >= MAX_CMD_LEN) die("command line too long"); conn->in = conn->out = -1; conn->argv = arg = xcalloc(7, sizeof(*arg)); if (protocol == PROTO_SSH) { const char *ssh = getenv("GIT_SSH"); int putty = ssh && strcasestr(ssh, "plink"); if (!ssh) ssh = "ssh"; *arg++ = ssh; if (putty && !strcasestr(ssh, "tortoiseplink")) *arg++ = "-batch"; if (port) { /* P is for PuTTY, p is for OpenSSH */ *arg++ = putty ? "-P" : "-p"; *arg++ = port; } *arg++ = host; } else { /* remove these from the environment */ const char *env[] = { ALTERNATE_DB_ENVIRONMENT, DB_ENVIRONMENT, GIT_DIR_ENVIRONMENT, GIT_WORK_TREE_ENVIRONMENT, GRAFT_ENVIRONMENT, INDEX_ENVIRONMENT, NO_REPLACE_OBJECTS_ENVIRONMENT, NULL }; conn->env = env; conn->use_shell = 1; } *arg++ = cmd.buf; *arg = NULL; if (start_command(conn)) die("unable to fork"); fd[0] = conn->out; /* read from child's stdout */ fd[1] = conn->in; /* write to child's stdin */ strbuf_release(&cmd); free(url); if (free_path) free(path); return conn; }
int offset_1st_component(const char *path) { if (has_dos_drive_prefix(path)) return 2 + is_dir_sep(path[2]); return is_dir_sep(path[0]); }
/* * 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); static char cwd[PATH_MAX+1]; const char *gitdirenv, *ret; char *gitfile; int len, offset, ceil_offset; dev_t current_device = 0; int one_filesystem = 1; /* * Let's assume that we are in a git repository. * If it turns out later that we are somewhere else, the value will be * updated accordingly. */ if (nongit_ok) *nongit_ok = 0; if (!getcwd(cwd, sizeof(cwd)-1)) die_errno("Unable to read current working directory"); offset = len = strlen(cwd); /* * If GIT_DIR is set explicitly, we're not going * to do any discovery, but we still do repository * validation. */ gitdirenv = getenv(GIT_DIR_ENVIRONMENT); if (gitdirenv) return setup_explicit_git_dir(gitdirenv, cwd, len, nongit_ok); ceil_offset = longest_ancestor_length(cwd, env_ceiling_dirs); if (ceil_offset < 0 && has_dos_drive_prefix(cwd)) ceil_offset = 1; /* * Test in the following order (relative to the cwd): * - .git (file containing "gitdir: <path>") * - .git/ * - ./ (bare) * - ../.git * - ../.git/ * - ../ (bare) * - ../../.git/ * etc. */ one_filesystem = !git_env_bool("GIT_DISCOVERY_ACROSS_FILESYSTEM", 0); if (one_filesystem) current_device = get_device_or_die(".", NULL); for (;;) { gitfile = (char*)read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (gitfile) gitdirenv = gitfile = xstrdup(gitfile); else { if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT)) gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; } if (gitdirenv) { ret = setup_discovered_git_dir(gitdirenv, cwd, offset, len, nongit_ok); free(gitfile); return ret; } free(gitfile); if (is_git_directory(".")) return setup_bare_git_dir(cwd, offset, len, nongit_ok); while (--offset > ceil_offset && cwd[offset] != '/'); if (offset <= ceil_offset) return setup_nongit(cwd, nongit_ok); if (one_filesystem) { dev_t parent_device = get_device_or_die("..", cwd); if (parent_device != current_device) { if (nongit_ok) { if (chdir(cwd)) die_errno("Cannot come back to cwd"); *nongit_ok = 1; return NULL; } cwd[offset] = '\0'; die("Not a git repository (or any parent up to mount parent %s)\n" "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).", cwd); } } if (chdir("..")) { cwd[offset] = '\0'; die_errno("Cannot change to '%s/..'", cwd); } } }
/* * Extract protocol and relevant parts from the specified connection URL. * The caller must free() the returned strings. */ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, char **ret_path) { char *url; char *host, *path; char *end; int separator = '/'; enum protocol protocol = PROTO_LOCAL; if (is_url(url_orig)) url = url_decode(url_orig); else url = xstrdup(url_orig); host = strstr(url, "://"); if (host) { *host = '\0'; protocol = get_protocol(url); host += 3; } else { host = url; if (!url_is_local_not_ssh(url)) { protocol = PROTO_SSH; separator = ':'; } } /* * Don't do destructive transforms as protocol code does * '[]' unwrapping in get_host_and_port() */ end = host_end(&host, 0); if (protocol == PROTO_LOCAL) path = end; else if (protocol == PROTO_FILE && has_dos_drive_prefix(end)) path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */ else path = strchr(end, separator); if (!path || !*path) die("No path specified. See 'man git-pull' for valid url syntax"); /* * null-terminate hostname and point path to ~ for URL's like this: * ssh://host.xz/~user/repo */ end = path; /* Need to \0 terminate host here */ if (separator == ':') path++; /* path starts after ':' */ if (protocol == PROTO_GIT || protocol == PROTO_SSH) { if (path[1] == '~') path++; } path = xstrdup(path); *end = '\0'; *ret_host = xstrdup(host); *ret_path = path; free(url); return protocol; }
static int sanitary_path_copy(char *dst, const char *src) { char *dst0; if (has_dos_drive_prefix(src)) { *dst++ = *src++; *dst++ = *src++; } dst0 = dst; if (is_dir_sep(*src)) { *dst++ = '/'; while (is_dir_sep(*src)) src++; } for (;;) { char c = *src; /* * A path component that begins with . could be * special: * (1) "." and ends -- ignore and terminate. * (2) "./" -- ignore them, eat slash and continue. * (3) ".." and ends -- strip one and terminate. * (4) "../" -- strip one, eat slash and continue. */ if (c == '.') { if (!src[1]) { /* (1) */ src++; } else if (is_dir_sep(src[1])) { /* (2) */ src += 2; while (is_dir_sep(*src)) src++; continue; } else if (src[1] == '.') { if (!src[2]) { /* (3) */ src += 2; goto up_one; } else if (is_dir_sep(src[2])) { /* (4) */ src += 3; while (is_dir_sep(*src)) src++; goto up_one; } } } /* copy up to the next '/', and eat all '/' */ while ((c = *src++) != '\0' && !is_dir_sep(c)) *dst++ = c; if (is_dir_sep(c)) { *dst++ = '/'; while (is_dir_sep(c)) c = *src++; src--; } else if (!c) break; continue; up_one: /* * dst0..dst is prefix portion, and dst[-1] is '/'; * go up one level. */ dst -= 2; /* go past trailing '/' if any */ if (dst < dst0) return -1; while (1) { if (dst <= dst0) break; c = *dst--; if (c == '/') { /* MinGW: cannot be '\\' anymore */ dst += 2; break; } } } *dst = '\0'; return 0; }