summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2010-08-04 01:10:29 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2010-08-04 01:10:29 (UTC)
commitaec9c245e7eaf444c7ae1851e3eda3b30748950f (patch) (unidiff)
tree04d5e945781441f2589ae8eb44bee8d5a2b17c2b
parente6c960c7c0f0d2e54b51cc43ef190df3ce52755e (diff)
parent119397b175874bd606952e93b7249ae4ffb9afbe (diff)
downloadcgit-aec9c245e7eaf444c7ae1851e3eda3b30748950f.zip
cgit-aec9c245e7eaf444c7ae1851e3eda3b30748950f.tar.gz
cgit-aec9c245e7eaf444c7ae1851e3eda3b30748950f.tar.bz2
Merge branch 'jd/gitolite'
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c33
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt21
-rw-r--r--scan-tree.c73
-rw-r--r--scan-tree.h3
5 files changed, 116 insertions, 17 deletions
diff --git a/cgit.c b/cgit.c
index c263872..eff5b7a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -132,12 +132,14 @@ void config_cb(const char *name, const char *value)
132 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
133 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
134 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
136 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
137 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
138 else if (!strcmp(name, "enable-gitweb-owner"))
139 ctx.cfg.enable_gitweb_owner = atoi(value);
138 else if (!strcmp(name, "enable-index-links")) 140 else if (!strcmp(name, "enable-index-links"))
139 ctx.cfg.enable_index_links = atoi(value); 141 ctx.cfg.enable_index_links = atoi(value);
140 else if (!strcmp(name, "enable-log-filecount")) 142 else if (!strcmp(name, "enable-log-filecount"))
141 ctx.cfg.enable_log_filecount = atoi(value); 143 ctx.cfg.enable_log_filecount = atoi(value);
142 else if (!strcmp(name, "enable-log-linecount")) 144 else if (!strcmp(name, "enable-log-linecount"))
143 ctx.cfg.enable_log_linecount = atoi(value); 145 ctx.cfg.enable_log_linecount = atoi(value);
@@ -178,15 +180,20 @@ void config_cb(const char *name, const char *value)
178 else if (!strcmp(name, "max-blob-size")) 180 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value); 181 ctx.cfg.max_blob_size = atoi(value);
180 else if (!strcmp(name, "max-repo-count")) 182 else if (!strcmp(name, "max-repo-count"))
181 ctx.cfg.max_repo_count = atoi(value); 183 ctx.cfg.max_repo_count = atoi(value);
182 else if (!strcmp(name, "max-commit-count")) 184 else if (!strcmp(name, "max-commit-count"))
183 ctx.cfg.max_commit_count = atoi(value); 185 ctx.cfg.max_commit_count = atoi(value);
186 else if (!strcmp(name, "project-list"))
187 ctx.cfg.project_list = xstrdup(expand_macros(value));
184 else if (!strcmp(name, "scan-path")) 188 else if (!strcmp(name, "scan-path"))
185 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 189 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
186 process_cached_repolist(expand_macros(value)); 190 process_cached_repolist(expand_macros(value));
191 else if (ctx.cfg.project_list)
192 scan_projects(expand_macros(value),
193 ctx.cfg.project_list, repo_config);
187 else 194 else
188 scan_tree(expand_macros(value), repo_config); 195 scan_tree(expand_macros(value), repo_config);
189 else if (!strcmp(name, "source-filter")) 196 else if (!strcmp(name, "source-filter"))
190 ctx.cfg.source_filter = new_filter(value, 1); 197 ctx.cfg.source_filter = new_filter(value, 1);
191 else if (!strcmp(name, "summary-log")) 198 else if (!strcmp(name, "summary-log"))
192 ctx.cfg.summary_log = atoi(value); 199 ctx.cfg.summary_log = atoi(value);
@@ -197,12 +204,14 @@ void config_cb(const char *name, const char *value)
197 else if (!strcmp(name, "side-by-side-diffs")) 204 else if (!strcmp(name, "side-by-side-diffs"))
198 ctx.cfg.ssdiff = atoi(value); 205 ctx.cfg.ssdiff = atoi(value);
199 else if (!strcmp(name, "agefile")) 206 else if (!strcmp(name, "agefile"))
200 ctx.cfg.agefile = xstrdup(value); 207 ctx.cfg.agefile = xstrdup(value);
201 else if (!strcmp(name, "renamelimit")) 208 else if (!strcmp(name, "renamelimit"))
202 ctx.cfg.renamelimit = atoi(value); 209 ctx.cfg.renamelimit = atoi(value);
210 else if (!strcmp(name, "remove-suffix"))
211 ctx.cfg.remove_suffix = atoi(value);
203 else if (!strcmp(name, "robots")) 212 else if (!strcmp(name, "robots"))
204 ctx.cfg.robots = xstrdup(value); 213 ctx.cfg.robots = xstrdup(value);
205 else if (!strcmp(name, "clone-prefix")) 214 else if (!strcmp(name, "clone-prefix"))
206 ctx.cfg.clone_prefix = xstrdup(value); 215 ctx.cfg.clone_prefix = xstrdup(value);
207 else if (!strcmp(name, "local-time")) 216 else if (!strcmp(name, "local-time"))
208 ctx.cfg.local_time = atoi(value); 217 ctx.cfg.local_time = atoi(value);
@@ -283,22 +292,25 @@ static void prepare_context(struct cgit_context *ctx)
283 ctx->cfg.cache_root_ttl = 5; 292 ctx->cfg.cache_root_ttl = 5;
284 ctx->cfg.cache_scanrc_ttl = 15; 293 ctx->cfg.cache_scanrc_ttl = 15;
285 ctx->cfg.cache_static_ttl = -1; 294 ctx->cfg.cache_static_ttl = -1;
286 ctx->cfg.css = "/cgit.css"; 295 ctx->cfg.css = "/cgit.css";
287 ctx->cfg.logo = "/cgit.png"; 296 ctx->cfg.logo = "/cgit.png";
288 ctx->cfg.local_time = 0; 297 ctx->cfg.local_time = 0;
298 ctx->cfg.enable_gitweb_owner = 1;
289 ctx->cfg.enable_tree_linenumbers = 1; 299 ctx->cfg.enable_tree_linenumbers = 1;
290 ctx->cfg.max_repo_count = 50; 300 ctx->cfg.max_repo_count = 50;
291 ctx->cfg.max_commit_count = 50; 301 ctx->cfg.max_commit_count = 50;
292 ctx->cfg.max_lock_attempts = 5; 302 ctx->cfg.max_lock_attempts = 5;
293 ctx->cfg.max_msg_len = 80; 303 ctx->cfg.max_msg_len = 80;
294 ctx->cfg.max_repodesc_len = 80; 304 ctx->cfg.max_repodesc_len = 80;
295 ctx->cfg.max_blob_size = 0; 305 ctx->cfg.max_blob_size = 0;
296 ctx->cfg.max_stats = 0; 306 ctx->cfg.max_stats = 0;
297 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 307 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
308 ctx->cfg.project_list = NULL;
298 ctx->cfg.renamelimit = -1; 309 ctx->cfg.renamelimit = -1;
310 ctx->cfg.remove_suffix = 0;
299 ctx->cfg.robots = "index, nofollow"; 311 ctx->cfg.robots = "index, nofollow";
300 ctx->cfg.root_title = "Git repository browser"; 312 ctx->cfg.root_title = "Git repository browser";
301 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 313 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
302 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 314 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
303 ctx->cfg.section = ""; 315 ctx->cfg.section = "";
304 ctx->cfg.summary_branches = 10; 316 ctx->cfg.summary_branches = 10;
@@ -571,13 +583,16 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
571 if (errno != EEXIST) 583 if (errno != EEXIST)
572 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 584 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
573 locked_rc, strerror(errno), errno); 585 locked_rc, strerror(errno), errno);
574 return errno; 586 return errno;
575 } 587 }
576 idx = cgit_repolist.count; 588 idx = cgit_repolist.count;
577 scan_tree(path, repo_config); 589 if (ctx.cfg.project_list)
590 scan_projects(path, ctx.cfg.project_list, repo_config);
591 else
592 scan_tree(path, repo_config);
578 print_repolist(f, &cgit_repolist, idx); 593 print_repolist(f, &cgit_repolist, idx);
579 if (rename(locked_rc, cached_rc)) 594 if (rename(locked_rc, cached_rc))
580 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 595 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
581 locked_rc, cached_rc, strerror(errno), errno); 596 locked_rc, cached_rc, strerror(errno), errno);
582 fclose(f); 597 fclose(f);
583 return 0; 598 return 0;
@@ -585,23 +600,31 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
585 600
586static void process_cached_repolist(const char *path) 601static void process_cached_repolist(const char *path)
587{ 602{
588 struct stat st; 603 struct stat st;
589 char *cached_rc; 604 char *cached_rc;
590 time_t age; 605 time_t age;
606 unsigned long hash;
591 607
592 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 608 hash = hash_str(path);
593 hash_str(path))); 609 if (ctx.cfg.project_list)
610 hash += hash_str(ctx.cfg.project_list);
611 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash));
594 612
595 if (stat(cached_rc, &st)) { 613 if (stat(cached_rc, &st)) {
596 /* Nothing is cached, we need to scan without forking. And 614 /* Nothing is cached, we need to scan without forking. And
597 * if we fail to generate a cached repolist, we need to 615 * if we fail to generate a cached repolist, we need to
598 * invoke scan_tree manually. 616 * invoke scan_tree manually.
599 */ 617 */
600 if (generate_cached_repolist(path, cached_rc)) 618 if (generate_cached_repolist(path, cached_rc)) {
601 scan_tree(path, repo_config); 619 if (ctx.cfg.project_list)
620 scan_projects(path, ctx.cfg.project_list,
621 repo_config);
622 else
623 scan_tree(path, repo_config);
624 }
602 return; 625 return;
603 } 626 }
604 627
605 parse_configfile(cached_rc, config_cb); 628 parse_configfile(cached_rc, config_cb);
606 629
607 /* If the cached configfile hasn't expired, lets exit now */ 630 /* If the cached configfile hasn't expired, lets exit now */
diff --git a/cgit.h b/cgit.h
index 32d9d2b..4090cd4 100644
--- a/cgit.h
+++ b/cgit.h
@@ -164,12 +164,13 @@ struct cgit_config {
164 char *header; 164 char *header;
165 char *index_header; 165 char *index_header;
166 char *index_info; 166 char *index_info;
167 char *logo; 167 char *logo;
168 char *logo_link; 168 char *logo_link;
169 char *module_link; 169 char *module_link;
170 char *project_list;
170 char *robots; 171 char *robots;
171 char *root_title; 172 char *root_title;
172 char *root_desc; 173 char *root_desc;
173 char *root_readme; 174 char *root_readme;
174 char *script_name; 175 char *script_name;
175 char *section; 176 char *section;
@@ -180,12 +181,13 @@ struct cgit_config {
180 int cache_repo_ttl; 181 int cache_repo_ttl;
181 int cache_root_ttl; 182 int cache_root_ttl;
182 int cache_scanrc_ttl; 183 int cache_scanrc_ttl;
183 int cache_static_ttl; 184 int cache_static_ttl;
184 int embedded; 185 int embedded;
185 int enable_filter_overrides; 186 int enable_filter_overrides;
187 int enable_gitweb_owner;
186 int enable_index_links; 188 int enable_index_links;
187 int enable_log_filecount; 189 int enable_log_filecount;
188 int enable_log_linecount; 190 int enable_log_linecount;
189 int enable_remote_branches; 191 int enable_remote_branches;
190 int enable_subject_links; 192 int enable_subject_links;
191 int enable_tree_linenumbers; 193 int enable_tree_linenumbers;
@@ -199,12 +201,13 @@ struct cgit_config {
199 int max_blob_size; 201 int max_blob_size;
200 int max_stats; 202 int max_stats;
201 int nocache; 203 int nocache;
202 int noplainemail; 204 int noplainemail;
203 int noheader; 205 int noheader;
204 int renamelimit; 206 int renamelimit;
207 int remove_suffix;
205 int snapshots; 208 int snapshots;
206 int summary_branches; 209 int summary_branches;
207 int summary_log; 210 int summary_log;
208 int summary_tags; 211 int summary_tags;
209 int ssdiff; 212 int ssdiff;
210 struct string_list mimetypes; 213 struct string_list mimetypes;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index a853522..5d77973 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -92,12 +92,17 @@ embedded::
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path.
102
98enable-index-links:: 103enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 104 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 105 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 106 "commit" and "tree" pages). Default value: "0".
102 107
103enable-log-filecount:: 108enable-log-filecount::
@@ -221,12 +226,22 @@ noplainemail::
221 Default value: "0". 226 Default value: "0".
222 227
223noheader:: 228noheader::
224 Flag which, when set to "1", will make cgit omit the standard header 229 Flag which, when set to "1", will make cgit omit the standard header
225 on all pages. Default value: none. See also: "embedded". 230 on all pages. Default value: none. See also: "embedded".
226 231
232project-list::
233 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path.
236
237remove-suffix::
238 If set to "1" and scan-path is enabled, if any repositories are found
239 with a suffix of ".git", this suffix will be removed for the url and
240 name. Default value: "0". See also: scan-path.
241
227renamelimit:: 242renamelimit::
228 Maximum number of files to consider when detecting renames. The value 243 Maximum number of files to consider when detecting renames. The value
229 "-1" uses the compiletime value in git (for further info, look at 244 "-1" uses the compiletime value in git (for further info, look at
230 `man git-diff`). Default value: "-1". 245 `man git-diff`). Default value: "-1".
231 246
232repo.group:: 247repo.group::
@@ -250,13 +265,16 @@ root-title::
250 Text printed as heading on the repository index page. Default value: 265 Text printed as heading on the repository index page. Default value:
251 "Git Repository Browser". 266 "Git Repository Browser".
252 267
253scan-path:: 268scan-path::
254 A path which will be scanned for repositories. If caching is enabled, 269 A path which will be scanned for repositories. If caching is enabled,
255 the result will be cached as a cgitrc include-file in the cache 270 the result will be cached as a cgitrc include-file in the cache
256 directory. Default value: none. See also: cache-scanrc-ttl. 271 directory. If project-list has been defined prior to scan-path,
272 scan-path loads only the directories listed in the file pointed to by
273 project-list. Default value: none. See also: cache-scanrc-ttl,
274 project-list.
257 275
258section:: 276section::
259 The name of the current repository section - all repositories defined 277 The name of the current repository section - all repositories defined
260 after this option will inherit the current section name. Default value: 278 after this option will inherit the current section name. Default value:
261 none. 279 none.
262 280
@@ -527,6 +545,7 @@ will generate the following html element:
527 545
528 546
529 547
530AUTHOR 548AUTHOR
531------ 549------
532Lars Hjemli <hjemli@gmail.com> 550Lars Hjemli <hjemli@gmail.com>
551Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/scan-tree.c b/scan-tree.c
index 1e18f3c..e987824 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,6 +1,15 @@
1/* scan-tree.c
2 *
3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 *
6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text)
8 */
9
1#include "cgit.h" 10#include "cgit.h"
2#include "configfile.h" 11#include "configfile.h"
3#include "html.h" 12#include "html.h"
4 13
5#define MAX_PATH 4096 14#define MAX_PATH 4096
6 15
@@ -35,18 +44,26 @@ static int is_git_dir(const char *path)
35 44
36 return 1; 45 return 1;
37} 46}
38 47
39struct cgit_repo *repo; 48struct cgit_repo *repo;
40repo_config_fn config_fn; 49repo_config_fn config_fn;
50char *owner;
41 51
42static void repo_config(const char *name, const char *value) 52static void repo_config(const char *name, const char *value)
43{ 53{
44 config_fn(repo, name, value); 54 config_fn(repo, name, value);
45} 55}
46 56
57static int git_owner_config(const char *key, const char *value, void *cb)
58{
59 if (!strcmp(key, "gitweb.owner"))
60 owner = xstrdup(value);
61 return 0;
62}
63
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 64static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 65{
49 struct stat st; 66 struct stat st;
50 struct passwd *pwd; 67 struct passwd *pwd;
51 char *p; 68 char *p;
52 size_t size; 69 size_t size;
@@ -55,32 +72,42 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
55 fprintf(stderr, "Error accessing %s: %s (%d)\n", 72 fprintf(stderr, "Error accessing %s: %s (%d)\n",
56 path, strerror(errno), errno); 73 path, strerror(errno), errno);
57 return; 74 return;
58 } 75 }
59 if (!stat(fmt("%s/noweb", path), &st)) 76 if (!stat(fmt("%s/noweb", path), &st))
60 return; 77 return;
61 if ((pwd = getpwuid(st.st_uid)) == NULL) { 78
62 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 79 owner = NULL;
63 path, strerror(errno), errno); 80 if (ctx.cfg.enable_gitweb_owner)
64 return; 81 git_config_from_file(git_owner_config, fmt("%s/config", path), NULL);
65 }
66 if (base == path) 82 if (base == path)
67 p = fmt("%s", path); 83 p = fmt("%s", path);
68 else 84 else
69 p = fmt("%s", path + strlen(base) + 1); 85 p = fmt("%s", path + strlen(base) + 1);
70 86
71 if (!strcmp(p + strlen(p) - 5, "/.git")) 87 if (!strcmp(p + strlen(p) - 5, "/.git"))
72 p[strlen(p) - 5] = '\0'; 88 p[strlen(p) - 5] = '\0';
73 89
74 repo = cgit_add_repo(xstrdup(p)); 90 repo = cgit_add_repo(xstrdup(p));
91 if (ctx.cfg.remove_suffix)
92 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
93 *p = '\0';
75 repo->name = repo->url; 94 repo->name = repo->url;
76 repo->path = xstrdup(path); 95 repo->path = xstrdup(path);
77 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 96 while (!owner) {
78 if (p) 97 if ((pwd = getpwuid(st.st_uid)) == NULL) {
79 *p = '\0'; 98 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
80 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 99 path, strerror(errno), errno);
100 break;
101 }
102 if (pwd->pw_gecos)
103 if ((p = strchr(pwd->pw_gecos, ',')))
104 *p = '\0';
105 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
106 }
107 repo->owner = owner;
81 108
82 p = fmt("%s/description", path); 109 p = fmt("%s/description", path);
83 if (!stat(p, &st)) 110 if (!stat(p, &st))
84 readfile(p, &repo->desc, &size); 111 readfile(p, &repo->desc, &size);
85 112
86 p = fmt("%s/README.html", path); 113 p = fmt("%s/README.html", path);
@@ -139,10 +166,38 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
139 scan_path(base, buf, fn); 166 scan_path(base, buf, fn);
140 free(buf); 167 free(buf);
141 } 168 }
142 closedir(dir); 169 closedir(dir);
143} 170}
144 171
172#define lastc(s) s[strlen(s) - 1]
173
174void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
175{
176 char line[MAX_PATH * 2], *z;
177 FILE *projects;
178 int err;
179
180 projects = fopen(projectsfile, "r");
181 if (!projects) {
182 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
183 projectsfile, strerror(errno), errno);
184 }
185 while (fgets(line, sizeof(line), projects) != NULL) {
186 for (z = &lastc(line);
187 strlen(line) && strchr("\n\r", *z);
188 z = &lastc(line))
189 *z = '\0';
190 if (strlen(line))
191 scan_path(path, fmt("%s/%s", path, line), fn);
192 }
193 if ((err = ferror(projects))) {
194 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
195 projectsfile, strerror(err), err);
196 }
197 fclose(projects);
198}
199
145void scan_tree(const char *path, repo_config_fn fn) 200void scan_tree(const char *path, repo_config_fn fn)
146{ 201{
147 scan_path(path, path, fn); 202 scan_path(path, path, fn);
148} 203}
diff --git a/scan-tree.h b/scan-tree.h
index 11539f4..1afbd4b 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,2 @@
1 1extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
2
3extern void scan_tree(const char *path, repo_config_fn fn); 2extern void scan_tree(const char *path, repo_config_fn fn);