diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Restyle the sources and fix the audit's findings
Diffstat (limited to 'source/scan-tree.c')
-rw-r--r--source/scan-tree.c254
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, &current_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)) {