diff options
Diffstat (limited to 'source/scan-tree.c')
| -rw-r--r-- | source/scan-tree.c | 254 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 136 insertions, 118 deletions
diff --git a/source/scan-tree.c b/source/scan-tree.c index 89f65c9..4a1444f 100644 --- a/source/scan-tree.c +++ b/source/scan-tree.c @@ -1,86 +1,120 @@ -/* scan-tree.c - * - * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> - * - * Licensed under GNU General Public License v2 - * (see LICENSE.txt for full license text) +/* + * Discovery of repositories on disk, so that an administrator can point cgit + * at a directory instead of naming every repository in cgitrc. The tree below + * that directory is walked for bare repositories and working trees, or when a + * project list is configured only the paths it names are visited. Every + * repository found is registered under its path relative to the root of the + * scan, with owner, description and section taken from the files git keeps + * beside it, and a cgitrc in the repository has the last word over all of them. */ #include "cgit.h" +#include "config.h" #include "scan-tree.h" -#include "configfile.h" -#include "html.h" -#include <config.h> +#include "shared.h" -// The description git puts in every freshly created repository. +// Git writes this line into the description file of every repository it +// creates, so a repository still carrying it gets cgit's own default instead. static const char *default_git_desc = "Unnamed repository; edit this file 'description' to name the repository."; -static struct cgit_repo *repo; +// The config callbacks are handed only a name and a value, so the repository +// being filled in waits here for the length of one add_repo call. +static struct cgit_repo *current_repo; -/* return 1 if path contains a objects/ directory and a HEAD file */ -static int is_git_dir(const char *path) +static int stat_entry(const char *dir, const char *name, struct stat *st) { - struct stat st; - struct strbuf pathbuf = STRBUF_INIT; - int result = 0; - - strbuf_addf(&pathbuf, "%s/objects", path); - if (stat(pathbuf.buf, &st)) { - if (errno != ENOENT) - fprintf(stderr, "Error checking path %s: %s (%d)\n", - path, strerror(errno), errno); - goto out; - } - if (!S_ISDIR(st.st_mode)) - goto out; - - strbuf_reset(&pathbuf); - strbuf_addf(&pathbuf, "%s/HEAD", path); - if (stat(pathbuf.buf, &st)) { - if (errno != ENOENT) - fprintf(stderr, "Error checking path %s: %s (%d)\n", - path, strerror(errno), errno); - goto out; - } - if (!S_ISREG(st.st_mode)) - goto out; + struct strbuf path = STRBUF_INIT; + int err; - result = 1; -out: - strbuf_release(&pathbuf); - return result; + strbuf_addf(&path, "%s/%s", dir, name); + err = stat(path.buf, st); + // A missing entry is the ordinary answer for a directory that is not a + // repository, so only some other failure is worth reporting. + if (err && errno != ENOENT) + fprintf(stderr, "Error checking path %s: %s (%d)\n", + dir, strerror(errno), errno); + strbuf_release(&path); + return err; } -static void scan_tree_repo_config(const char *name, const char *value) +static int is_git_dir(const char *path) { - cgit_repo_config(repo, name, value); + struct stat st; + + if (stat_entry(path, "objects", &st) || !S_ISDIR(st.st_mode)) + return 0; + if (stat_entry(path, "HEAD", &st) || !S_ISREG(st.st_mode)) + return 0; + return 1; } -static int gitconfig_config(const char *key, const char *value, +static int apply_gitconfig(const char *key, const char *value, const __attribute__((unused)) struct config_context *cfg_ctx, void *cb) { const char *name; if (!strcmp(key, "gitweb.owner")) - cgit_repo_config(repo, "owner", value); + cgit_repo_config(current_repo, "owner", value); else if (!strcmp(key, "gitweb.description")) - cgit_repo_config(repo, "desc", value); + cgit_repo_config(current_repo, "desc", value); else if (!strcmp(key, "gitweb.category")) - cgit_repo_config(repo, "section", value); + cgit_repo_config(current_repo, "section", value); else if (!strcmp(key, "gitweb.homepage")) - cgit_repo_config(repo, "homepage", value); + cgit_repo_config(current_repo, "homepage", value); else if (skip_prefix(key, "cgit.", &name)) - cgit_repo_config(repo, name, value); + cgit_repo_config(current_repo, name, value); return 0; } -static char *xstrrchr(char *s, char *from, int c) +static void apply_cgitrc(const char *name, const char *value) { - while (from >= s && *from != c) + cgit_repo_config(current_repo, name, value); +} + +static char *find_char_back(char *start, char *from, int c) +{ + while (from >= start && *from != c) from--; - return from < s ? NULL : from; + return from < start ? NULL : from; +} + +/* + * A positive depth counts separators from the left of the path and a negative + * one counts back from the right. + */ +static char *section_slash(struct strbuf *relpath, int depth) +{ + char *slash; + + if (depth > 0) { + slash = relpath->buf - 1; + while (slash && depth && (slash = strchr(slash + 1, '/'))) + depth--; + } else { + slash = relpath->buf + relpath->len; + while (slash && depth && + (slash = find_char_back(relpath->buf, slash - 1, '/'))) + depth++; + } + return slash && !depth ? slash : NULL; +} + +static void set_section_from_path(struct strbuf *relpath, int depth) +{ + char *slash = section_slash(relpath, depth); + + if (!slash) + return; + *slash = '\0'; + current_repo->section = cgit_strdup_first_line(relpath->buf); + *slash = '/'; + if (starts_with(current_repo->name, current_repo->section)) { + current_repo->name += strlen(current_repo->section); + if (*current_repo->name == '/') + current_repo->name++; + } } static void add_repo(const char *base, struct strbuf *path) @@ -88,10 +122,9 @@ static void add_repo(const char *base, struct strbuf *path) struct stat st; struct passwd *pwd; size_t pathlen; - struct strbuf rel = STRBUF_INIT; - char *p, *slash; - int n; - size_t size; + struct strbuf relpath = STRBUF_INIT; + char *comma; + size_t desc_size; if (stat(path->buf, &st)) { fprintf(stderr, "Error accessing %s: %s (%d)\n", @@ -104,7 +137,7 @@ static void add_repo(const char *base, struct strbuf *path) if (ctx.cfg.strict_export) { strbuf_addstr(path, ctx.cfg.strict_export); - if(stat(path->buf, &st)) + if (stat(path->buf, &st)) return; strbuf_setlen(path, pathlen); } @@ -115,86 +148,79 @@ static void add_repo(const char *base, struct strbuf *path) strbuf_setlen(path, pathlen); if (!starts_with(path->buf, base)) - strbuf_addbuf(&rel, path); + strbuf_addbuf(&relpath, path); else - strbuf_addstr(&rel, path->buf + strlen(base) + 1); + strbuf_addstr(&relpath, path->buf + strlen(base) + 1); // Drop the trailing slash added above before testing for "/.git", since // with it still attached the suffix never matches and every ordinary // working tree is named "repo/.git" instead of "repo". - if (rel.len && rel.buf[rel.len - 1] == '/') - strbuf_setlen(&rel, rel.len - 1); - if (rel.len >= 5 && !strcmp(rel.buf + rel.len - 5, "/.git")) - strbuf_setlen(&rel, rel.len - 5); + if (relpath.len && relpath.buf[relpath.len - 1] == '/') + strbuf_setlen(&relpath, relpath.len - 1); + if (relpath.len >= 5 && !strcmp(relpath.buf + relpath.len - 5, "/.git")) + strbuf_setlen(&relpath, relpath.len - 5); - repo = cgit_add_repo(rel.buf); + current_repo = cgit_add_repo(relpath.buf); if (ctx.cfg.enable_git_config) { strbuf_addstr(path, "config"); - git_config_from_file(gitconfig_config, path->buf, NULL); + git_config_from_file(apply_gitconfig, path->buf, NULL); strbuf_setlen(path, pathlen); } if (ctx.cfg.remove_suffix) { size_t urllen; - strip_suffix(repo->url, ".git", &urllen); - strip_suffix_mem(repo->url, &urllen, "/"); - repo->url[urllen] = '\0'; + strip_suffix(current_repo->url, ".git", &urllen); + strip_suffix_mem(current_repo->url, &urllen, "/"); + current_repo->url[urllen] = '\0'; } - repo->path = cgit_strdup_first_line(path->buf); - while (!repo->owner) { + current_repo->path = cgit_strdup_first_line(path->buf); + while (!current_repo->owner) { if ((pwd = getpwuid(st.st_uid)) == NULL) { fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", path->buf, strerror(errno), errno); break; } + // A gecos field puts the owner's name in front of a comma + // separated list of office and phone details. if (pwd->pw_gecos) - if ((p = strchr(pwd->pw_gecos, ','))) - *p = '\0'; - repo->owner = cgit_strdup_first_line(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name); + if ((comma = strchr(pwd->pw_gecos, ','))) + *comma = '\0'; + current_repo->owner = cgit_strdup_first_line( + pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name); } - if (repo->desc == cgit_default_repo_desc || !repo->desc) { + if (current_repo->desc == cgit_default_repo_desc || !current_repo->desc) { strbuf_addstr(path, "description"); if (!stat(path->buf, &st)) - cgit_read_first_line(path->buf, &repo->desc, &size); + cgit_read_first_line(path->buf, ¤t_repo->desc, + &desc_size); strbuf_setlen(path, pathlen); - // Git writes this line into every repository it creates, so it - // describes nothing. Treat it as no description at all rather - // than repeating it down the whole index. - if (repo->desc && !strcmp(repo->desc, default_git_desc)) { - free(repo->desc); - repo->desc = cgit_default_repo_desc; + if (current_repo->desc && + !strcmp(current_repo->desc, default_git_desc)) { + free(current_repo->desc); + current_repo->desc = cgit_default_repo_desc; } } - if (ctx.cfg.section_from_path) { - n = ctx.cfg.section_from_path; - if (n > 0) { - slash = rel.buf - 1; - while (slash && n && (slash = strchr(slash + 1, '/'))) - n--; - } else { - slash = rel.buf + rel.len; - while (slash && n && (slash = xstrrchr(rel.buf, slash - 1, '/'))) - n++; - } - if (slash && !n) { - *slash = '\0'; - repo->section = cgit_strdup_first_line(rel.buf); - *slash = '/'; - if (starts_with(repo->name, repo->section)) { - repo->name += strlen(repo->section); - if (*repo->name == '/') - repo->name++; - } - } - } + if (ctx.cfg.section_from_path) + set_section_from_path(&relpath, ctx.cfg.section_from_path); strbuf_addstr(path, "cgitrc"); if (!stat(path->buf, &st)) - parse_configfile(path->buf, &scan_tree_repo_config); + config_file_parse(path->buf, &apply_cgitrc); - strbuf_release(&rel); + strbuf_release(&relpath); +} + +static int should_scan(const struct dirent *ent) +{ + if (ent->d_name[0] != '.') + return 1; + if (ent->d_name[1] == '\0') + return 0; + if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') + return 0; + return ctx.cfg.scan_hidden_path; } static void scan_path(const char *base, const char *path) @@ -211,7 +237,7 @@ static void scan_path(const char *base, const char *path) return; } - strbuf_add(&pathbuf, path, strlen(path)); + strbuf_add(&pathbuf, path, pathlen); if (is_git_dir(pathbuf.buf)) { add_repo(base, &pathbuf); goto end; @@ -221,20 +247,12 @@ static void scan_path(const char *base, const char *path) add_repo(base, &pathbuf); goto end; } - /* - * Add one because we don't want to lose the trailing '/' when we - * reset the length of pathbuf in the loop below. - */ + // Take in the '/' that "/.git" left in the buffer, since the loop below + // truncates to this length and then appends an entry name straight on. pathlen++; while ((ent = readdir(dir)) != NULL) { - if (ent->d_name[0] == '.') { - if (ent->d_name[1] == '\0') - continue; - if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') - continue; - if (!ctx.cfg.scan_hidden_path) - continue; - } + if (!should_scan(ent)) + continue; strbuf_setlen(&pathbuf, pathlen); strbuf_addstr(&pathbuf, ent->d_name); if (stat(pathbuf.buf, &st)) { |
