teach config --blob option to parse config from database

This can be used to read configuration values directly from git's database. For example it is useful for reading to be checked out .gitmodules files directly from the database. Signed-off-by: Heiko Voigt <hvoigt@hvoigt.net> Acked-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Heiko Voigt committed Jul 12, 2013 at 00:46 UTC 1bc888193e1044db317a45b9a4c8d2b87b998f40
5 files changed +193 -7
Documentation/git-config.txt
+7
@@ -118,6 +118,13 @@ See also <<FILES>>.
118 --file config-file::
119 Use the given config file instead of the one specified by GIT_CONFIG.
120
121 +--blob blob::
122 + Similar to '--file' but use the given blob instead of a file. E.g.
123 + you can use 'master:.gitmodules' to read values from the file
124 + '.gitmodules' in the master branch. See "SPECIFYING REVISIONS"
125 + section in linkgit:gitrevisions[7] for a more complete list of
126 + ways to spell blob names.
127 +
128 --remove-section::
129 Remove the given section from the configuration file.
130
builtin/config.c
+27 -4
@@ -21,6 +21,7 @@ static char term = '\n';
21
22 static int use_global_config, use_system_config, use_local_config;
23 static const char *given_config_file;
24 +static const char *given_config_blob;
25 static int actions, types;
26 static const char *get_color_slot, *get_colorbool_slot;
27 static int end_null;
@@ -53,6 +54,7 @@ static struct option builtin_config_options[] = {
54 OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")),
55 OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")),
56 OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")),
57 + OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")),
58 OPT_GROUP(N_("Action")),
59 OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
60 OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
@@ -218,7 +220,8 @@ static int get_value(const char *key_, const char *regex_)
220 }
221
222 git_config_with_options(collect_config, &values,
221 - given_config_file, respect_includes);
223 + given_config_file, given_config_blob,
224 + respect_includes);
225
226 ret = !values.nr;
227
@@ -302,7 +305,8 @@ static void get_color(const char *def_color)
305 get_color_found = 0;
306 parsed_color[0] = '\0';
307 git_config_with_options(git_get_color_config, NULL,
305 - given_config_file, respect_includes);
308 + given_config_file, given_config_blob,
309 + respect_includes);
310
311 if (!get_color_found && def_color)
312 color_parse(def_color, "command line", parsed_color);
@@ -330,7 +334,8 @@ static int get_colorbool(int print)
334 get_colorbool_found = -1;
335 get_diff_color_found = -1;
336 git_config_with_options(git_get_colorbool_config, NULL,
333 - given_config_file, respect_includes);
337 + given_config_file, given_config_blob,
338 + respect_includes);
339
340 if (get_colorbool_found < 0) {
341 if (!strcmp(get_colorbool_slot, "color.diff"))
@@ -348,6 +353,12 @@ static int get_colorbool(int print)
353 return get_colorbool_found ? 0 : 1;
354 }
355
356 +static void check_blob_write(void)
357 +{
358 + if (given_config_blob)
359 + die("writing config blobs is not supported");
360 +}
361 +
362 int cmd_config(int argc, const char **argv, const char *prefix)
363 {
364 int nongit = !startup_info->have_repository;
@@ -359,7 +370,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
370 builtin_config_usage,
371 PARSE_OPT_STOP_AT_NON_OPTION);
372
362 - if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
373 + if (use_global_config + use_system_config + use_local_config +
374 + !!given_config_file + !!given_config_blob > 1) {
375 error("only one config file at a time.");
376 usage_with_options(builtin_config_usage, builtin_config_options);
377 }
@@ -438,6 +450,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
450 check_argc(argc, 0, 0);
451 if (git_config_with_options(show_all_config, NULL,
452 given_config_file,
453 + given_config_blob,
454 respect_includes) < 0) {
455 if (given_config_file)
456 die_errno("unable to read config file '%s'",
@@ -450,6 +463,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
463 check_argc(argc, 0, 0);
464 if (!given_config_file && nongit)
465 die("not in a git directory");
466 + if (given_config_blob)
467 + die("editing blobs is not supported");
468 git_config(git_default_config, NULL);
469 launch_editor(given_config_file ?
470 given_config_file : git_path("config"),
@@ -457,6 +472,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
472 }
473 else if (actions == ACTION_SET) {
474 int ret;
475 + check_blob_write();
476 check_argc(argc, 2, 2);
477 value = normalize_value(argv[0], argv[1]);
478 ret = git_config_set_in_file(given_config_file, argv[0], value);
@@ -466,18 +482,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
482 return ret;
483 }
484 else if (actions == ACTION_SET_ALL) {
485 + check_blob_write();
486 check_argc(argc, 2, 3);
487 value = normalize_value(argv[0], argv[1]);
488 return git_config_set_multivar_in_file(given_config_file,
489 argv[0], value, argv[2], 0);
490 }
491 else if (actions == ACTION_ADD) {
492 + check_blob_write();
493 check_argc(argc, 2, 2);
494 value = normalize_value(argv[0], argv[1]);
495 return git_config_set_multivar_in_file(given_config_file,
496 argv[0], value, "^$", 0);
497 }
498 else if (actions == ACTION_REPLACE_ALL) {
499 + check_blob_write();
500 check_argc(argc, 2, 3);
501 value = normalize_value(argv[0], argv[1]);
502 return git_config_set_multivar_in_file(given_config_file,
@@ -500,6 +519,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
519 return get_value(argv[0], argv[1]);
520 }
521 else if (actions == ACTION_UNSET) {
522 + check_blob_write();
523 check_argc(argc, 1, 2);
524 if (argc == 2)
525 return git_config_set_multivar_in_file(given_config_file,
@@ -509,12 +529,14 @@ int cmd_config(int argc, const char **argv, const char *prefix)
529 argv[0], NULL);
530 }
531 else if (actions == ACTION_UNSET_ALL) {
532 + check_blob_write();
533 check_argc(argc, 1, 2);
534 return git_config_set_multivar_in_file(given_config_file,
535 argv[0], NULL, argv[1], 1);
536 }
537 else if (actions == ACTION_RENAME_SECTION) {
538 int ret;
539 + check_blob_write();
540 check_argc(argc, 2, 2);
541 ret = git_config_rename_section_in_file(given_config_file,
542 argv[0], argv[1]);
@@ -525,6 +547,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
547 }
548 else if (actions == ACTION_REMOVE_SECTION) {
549 int ret;
550 + check_blob_write();
551 check_argc(argc, 1, 1);
552 ret = git_config_rename_section_in_file(given_config_file,
553 argv[0], NULL);
cache.h
+5 -1
@@ -1142,11 +1142,15 @@ extern int update_server_info(int);
1142 typedef int (*config_fn_t)(const char *, const char *, void *);
1143 extern int git_default_config(const char *, const char *, void *);
1144 extern int git_config_from_file(config_fn_t fn, const char *, void *);
1145 +extern int git_config_from_buf(config_fn_t fn, const char *name,
1146 + const char *buf, size_t len, void *data);
1147 extern void git_config_push_parameter(const char *text);
1148 extern int git_config_from_parameters(config_fn_t fn, void *data);
1149 extern int git_config(config_fn_t fn, void *);
1150 extern int git_config_with_options(config_fn_t fn, void *,
1149 - const char *filename, int respect_includes);
1151 + const char *filename,
1152 + const char *blob_ref,
1153 + int respect_includes);
1154 extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
1155 extern int git_parse_ulong(const char *, unsigned long *);
1156 extern int git_config_int(const char *, const char *);
config.c
+84 -2
@@ -14,6 +14,11 @@ struct config_source {
14 struct config_source *prev;
15 union {
16 FILE *file;
17 + struct config_buf {
18 + const char *buf;
19 + size_t len;
20 + size_t pos;
21 + } buf;
22 } u;
23 const char *name;
24 int linenr;
@@ -45,6 +50,28 @@ static long config_file_ftell(struct config_source *conf)
50 return ftell(conf->u.file);
51 }
52
53 +
54 +static int config_buf_fgetc(struct config_source *conf)
55 +{
56 + if (conf->u.buf.pos < conf->u.buf.len)
57 + return conf->u.buf.buf[conf->u.buf.pos++];
58 +
59 + return EOF;
60 +}
61 +
62 +static int config_buf_ungetc(int c, struct config_source *conf)
63 +{
64 + if (conf->u.buf.pos > 0)
65 + return conf->u.buf.buf[--conf->u.buf.pos];
66 +
67 + return EOF;
68 +}
69 +
70 +static long config_buf_ftell(struct config_source *conf)
71 +{
72 + return conf->u.buf.pos;
73 +}
74 +
75 #define MAX_INCLUDE_DEPTH 10
76 static const char include_depth_advice[] =
77 "exceeded maximum include depth (%d) while including\n"
@@ -961,6 +988,57 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)
988 return ret;
989 }
990
991 +int git_config_from_buf(config_fn_t fn, const char *name, const char *buf,
992 + size_t len, void *data)
993 +{
994 + struct config_source top;
995 +
996 + top.u.buf.buf = buf;
997 + top.u.buf.len = len;
998 + top.u.buf.pos = 0;
999 + top.name = name;
1000 + top.fgetc = config_buf_fgetc;
1001 + top.ungetc = config_buf_ungetc;
1002 + top.ftell = config_buf_ftell;
1003 +
1004 + return do_config_from(&top, fn, data);
1005 +}
1006 +
1007 +static int git_config_from_blob_sha1(config_fn_t fn,
1008 + const char *name,
1009 + const unsigned char *sha1,
1010 + void *data)
1011 +{
1012 + enum object_type type;
1013 + char *buf;
1014 + unsigned long size;
1015 + int ret;
1016 +
1017 + buf = read_sha1_file(sha1, &type, &size);
1018 + if (!buf)
1019 + return error("unable to load config blob object '%s'", name);
1020 + if (type != OBJ_BLOB) {
1021 + free(buf);
1022 + return error("reference '%s' does not point to a blob", name);
1023 + }
1024 +
1025 + ret = git_config_from_buf(fn, name, buf, size, data);
1026 + free(buf);
1027 +
1028 + return ret;
1029 +}
1030 +
1031 +static int git_config_from_blob_ref(config_fn_t fn,
1032 + const char *name,
1033 + void *data)
1034 +{
1035 + unsigned char sha1[20];
1036 +
1037 + if (get_sha1(name, sha1) < 0)
1038 + return error("unable to resolve config blob '%s'", name);
1039 + return git_config_from_blob_sha1(fn, name, sha1, data);
1040 +}
1041 +
1042 const char *git_etc_gitconfig(void)
1043 {
1044 static const char *system_wide;
@@ -1026,7 +1104,9 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
1104 }
1105
1106 int git_config_with_options(config_fn_t fn, void *data,
1029 - const char *filename, int respect_includes)
1107 + const char *filename,
1108 + const char *blob_ref,
1109 + int respect_includes)
1110 {
1111 char *repo_config = NULL;
1112 int ret;
@@ -1045,6 +1125,8 @@ int git_config_with_options(config_fn_t fn, void *data,
1125 */
1126 if (filename)
1127 return git_config_from_file(fn, filename, data);
1128 + else if (blob_ref)
1129 + return git_config_from_blob_ref(fn, blob_ref, data);
1130
1131 repo_config = git_pathdup("config");
1132 ret = git_config_early(fn, data, repo_config);
@@ -1055,7 +1137,7 @@ int git_config_with_options(config_fn_t fn, void *data,
1137
1138 int git_config(config_fn_t fn, void *data)
1139 {
1058 - return git_config_with_options(fn, data, NULL, 1);
1140 + return git_config_with_options(fn, data, NULL, NULL, 1);
1141 }
1142
1143 /*
t/t1307-config-blob.sh new
+70
@@ -0,0 +1,70 @@
1 +#!/bin/sh
2 +
3 +test_description='support for reading config from a blob'
4 +. ./test-lib.sh
5 +
6 +test_expect_success 'create config blob' '
7 + cat >config <<-\EOF &&
8 + [some]
9 + value = 1
10 + EOF
11 + git add config &&
12 + git commit -m foo
13 +'
14 +
15 +test_expect_success 'list config blob contents' '
16 + echo some.value=1 >expect &&
17 + git config --blob=HEAD:config --list >actual &&
18 + test_cmp expect actual
19 +'
20 +
21 +test_expect_success 'fetch value from blob' '
22 + echo true >expect &&
23 + git config --blob=HEAD:config --bool some.value >actual &&
24 + test_cmp expect actual
25 +'
26 +
27 +test_expect_success 'reading non-existing value from blob is an error' '
28 + test_must_fail git config --blob=HEAD:config non.existing
29 +'
30 +
31 +test_expect_success 'reading from blob and file is an error' '
32 + test_must_fail git config --blob=HEAD:config --system --list
33 +'
34 +
35 +test_expect_success 'reading from missing ref is an error' '
36 + test_must_fail git config --blob=HEAD:doesnotexist --list
37 +'
38 +
39 +test_expect_success 'reading from non-blob is an error' '
40 + test_must_fail git config --blob=HEAD --list
41 +'
42 +
43 +test_expect_success 'setting a value in a blob is an error' '
44 + test_must_fail git config --blob=HEAD:config some.value foo
45 +'
46 +
47 +test_expect_success 'deleting a value in a blob is an error' '
48 + test_must_fail git config --blob=HEAD:config --unset some.value
49 +'
50 +
51 +test_expect_success 'editing a blob is an error' '
52 + test_must_fail git config --blob=HEAD:config --edit
53 +'
54 +
55 +test_expect_success 'parse errors in blobs are properly attributed' '
56 + cat >config <<-\EOF &&
57 + [some]
58 + value = "
59 + EOF
60 + git add config &&
61 + git commit -m broken &&
62 +
63 + test_must_fail git config --blob=HEAD:config some.value 2>err &&
64 +
65 + # just grep for our token as the exact error message is likely to
66 + # change or be internationalized
67 + grep "HEAD:config" err
68 +'
69 +
70 +test_done