Commit 587926ef by Vicent Martí

Merge pull request #110 from carlosmn/config

Implement config parsing. Finally!
parents 335d6c99 29dca088
......@@ -52,5 +52,6 @@
#include "git2/tree.h"
#include "git2/index.h"
#include "git2/config.h"
#endif
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDE_git_config_h__
#define INCLUDE_git_config_h__
#include "common.h"
#include "types.h"
/**
* @file git2/config.h
* @brief Git config management routines
* @defgroup git_config Git config management routines
* @ingroup Git
* @{
*/
GIT_BEGIN_DECL
/**
* Allocate a new configuration
*/
GIT_EXTERN(int) git_config_new(git_config **out);
/**
* Open a configuration file
*
* @param cfg_out pointer to the configuration data
* @param path where to load the confiration from
*/
GIT_EXTERN(int) git_config_open_bare(git_config **cfg_out, const char *path);
/**
*
*/
GIT_EXTERN(int) git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority);
/**
* Free the configuration and its associated memory
*
* @param cfg the configuration to free
*/
GIT_EXTERN(void) git_config_free(git_config *cfg);
/**
* Get the value of an integer config variable.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable where the value should be stored
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_get_int(git_config *cfg, const char *name, int *out);
/**
* Get the value of a long integer config variable.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable where the value should be stored
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_get_long(git_config *cfg, const char *name, long int *out);
/**
* Get the value of a boolean config variable.
*
* This function uses the usual C convention of 0 being false and
* anything else true.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable where the value should be stored
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_get_bool(git_config *cfg, const char *name, int *out);
/**
* Get the value of a string config variable.
*
* The string is owned by the variable and should not be freed by the
* user.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable's value
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_get_string(git_config *cfg, const char *name, const char **out);
/**
* Set the value of an integer config variable.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable where the value should be stored
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_set_int(git_config *cfg, const char *name, int value);
/**
* Set the value of a long integer config variable.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param out pointer to the variable where the value should be stored
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_set_long(git_config *cfg, const char *name, long int value);
/**
* Set the value of a boolean config variable.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param value the value to store
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value);
/**
* Set the value of a string config variable.
*
* A copy of the string is made and the user is free to use it
* afterwards.
*
* @param cfg where to look for the variable
* @param name the variable's name
* @param value the string to store.
* @return GIT_SUCCESS on success; error code otherwise
*/
GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value);
/**
* Perform an operation on each config variable.
*
* The callback is passed a pointer to a config variable name and the
* data pointer passed to this function. As soon as one of the
* callback functions returns something other than 0, this function
* returns that value.
*
* @param cfg where to get the variables from
* @param callback the function to call on each variable
* @param data the data to pass to the callback
* @return GIT_SUCCESS or the return value of the callback which didn't return 0
*/
GIT_EXTERN(int) git_config_foreach(git_config *cfg, int (*callback)(const char *, void *data), void *data);
/** @} */
GIT_END_DECL
#endif
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDE_git_config_backend_h__
#define INCLUDE_git_config_backend_h__
#include "common.h"
#include "types.h"
#include "config.h"
GIT_BEGIN_DECL
struct git_config;
struct git_config_backend {
struct git_config *cfg;
/* Open means open the file/database and parse if necessary */
int (*open)(struct git_config_backend *);
int (* get)(struct git_config_backend *, const char *key, const char **value);
int (* set)(struct git_config_backend *, const char *key, const char *value);
int (*foreach)(struct git_config_backend *, int (*fn)(const char *, void *), void *data);
void (*free)(struct git_config_backend *);
};
/**
* Create a file-backed configuration backend
*
* @param out the new backend
* @path where the config file is located
*/
GIT_EXTERN(int) git_config_backend_file(struct git_config_backend **out, const char *path);
GIT_END_DECL
#endif
......@@ -130,6 +130,15 @@ typedef struct git_treebuilder git_treebuilder;
/** Memory representation of an index file. */
typedef struct git_index git_index;
/** Memory representation of a config file */
typedef struct git_config git_config;
/** A specific implementation of a config backend */
typedef struct git_config_backend git_config_backend;
/** Memory representation of a config variable */
typedef struct git_cvar git_cvar;
/** Time in a signature */
typedef struct git_time {
git_time_t time; /** time in seconds from epoch */
......
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "common.h"
#include "fileops.h"
#include "hashtable.h"
#include "config.h"
#include "git2/config_backend.h"
#include "vector.h"
#include <ctype.h>
typedef struct {
git_config_backend *backend;
int priority;
} backend_internal;
void git__strntolower(char *str, int len)
{
int i;
for (i = 0; i < len; ++i) {
str[i] = tolower(str[i]);
}
}
void git__strtolower(char *str)
{
git__strntolower(str, strlen(str));
}
int git_config_open_bare(git_config **out, const char *path)
{
git_config_backend *backend = NULL;
git_config *cfg = NULL;
int error = GIT_SUCCESS;
error = git_config_new(&cfg);
if (error < GIT_SUCCESS)
goto error;
error = git_config_backend_file(&backend, path);
if (error < GIT_SUCCESS)
goto error;
error = git_config_add_backend(cfg, backend, 1);
if (error < GIT_SUCCESS)
goto error;
error = backend->open(backend);
if (error < GIT_SUCCESS)
goto error;
*out = cfg;
return error;
error:
if(backend)
backend->free(backend);
return error;
}
void git_config_free(git_config *cfg)
{
unsigned int i;
git_config_backend *backend;
backend_internal *internal;
for(i = 0; i < cfg->backends.length; ++i){
internal = git_vector_get(&cfg->backends, i);
backend = internal->backend;
backend->free(backend);
free(internal);
}
git_vector_free(&cfg->backends);
free(cfg);
}
static int config_backend_cmp(const void *a, const void *b)
{
const backend_internal *bk_a = *(const backend_internal **)(a);
const backend_internal *bk_b = *(const backend_internal **)(b);
return bk_b->priority - bk_a->priority;
}
int git_config_new(git_config **out)
{
git_config *cfg;
cfg = git__malloc(sizeof(git_config));
if (cfg == NULL)
return GIT_ENOMEM;
memset(cfg, 0x0, sizeof(git_config));
if (git_vector_init(&cfg->backends, 3, config_backend_cmp) < 0) {
free(cfg);
return GIT_ENOMEM;
}
*out = cfg;
return GIT_SUCCESS;
}
int git_config_add_backend(git_config *cfg, git_config_backend *backend, int priority)
{
backend_internal *internal;
assert(cfg && backend);
internal = git__malloc(sizeof(backend_internal));
if (internal == NULL)
return GIT_ENOMEM;
internal->backend = backend;
internal->priority = priority;
if (git_vector_insert(&cfg->backends, internal) < 0) {
free(internal);
return GIT_ENOMEM;
}
git_vector_sort(&cfg->backends);
internal->backend->cfg = cfg;
return GIT_SUCCESS;
}
/*
* Loop over all the variables
*/
int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data)
{
int ret = GIT_SUCCESS;
unsigned int i;
backend_internal *internal;
git_config_backend *backend;
for(i = 0; i < cfg->backends.length && ret == 0; ++i) {
internal = git_vector_get(&cfg->backends, i);
backend = internal->backend;
ret = backend->foreach(backend, fn, data);
}
return ret;
}
/**************
* Setters
**************/
/*
* Internal function to actually set the string value of a variable
*/
int git_config_set_long(git_config *cfg, const char *name, long int value)
{
char str_value[5]; /* Most numbers should fit in here */
int buf_len = sizeof(str_value), ret;
char *help_buf = NULL;
if ((ret = snprintf(str_value, buf_len, "%ld", value)) >= buf_len - 1){
/* The number is too large, we need to allocate more memory */
buf_len = ret + 1;
help_buf = git__malloc(buf_len);
snprintf(help_buf, buf_len, "%ld", value);
ret = git_config_set_string(cfg, name, help_buf);
free(help_buf);
} else {
ret = git_config_set_string(cfg, name, str_value);
}
return ret;
}
int git_config_set_int(git_config *cfg, const char *name, int value)
{
return git_config_set_long(cfg, name, value);
}
int git_config_set_bool(git_config *cfg, const char *name, int value)
{
const char *str_value;
if (value == 0)
str_value = "false";
else
str_value = "true";
return git_config_set_string(cfg, name, str_value);
}
int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
backend_internal *internal;
git_config_backend *backend;
assert(cfg->backends.length > 0);
internal = git_vector_get(&cfg->backends, 0);
backend = internal->backend;
return backend->set(backend, name, value);
}
/***********
* Getters
***********/
int git_config_get_long(git_config *cfg, const char *name, long int *out)
{
const char *value, *num_end;
int ret;
long int num;
ret = git_config_get_string(cfg, name, &value);
if (ret < GIT_SUCCESS)
return ret;
ret = git__strtol32(&num, value, &num_end, 0);
if (ret < GIT_SUCCESS)
return ret;
switch (*num_end) {
case '\0':
break;
case 'k':
case 'K':
num *= 1024;
break;
case 'm':
case 'M':
num *= 1024 * 1024;
break;
case 'g':
case 'G':
num *= 1024 * 1024 * 1024;
break;
default:
return GIT_EINVALIDTYPE;
}
*out = num;
return GIT_SUCCESS;
}
int git_config_get_int(git_config *cfg, const char *name, int *out)
{
long int tmp;
int ret;
ret = git_config_get_long(cfg, name, &tmp);
*out = (int) tmp;
return ret;
}
int git_config_get_bool(git_config *cfg, const char *name, int *out)
{
const char *value;
int error = GIT_SUCCESS;
error = git_config_get_string(cfg, name, &value);
if (error < GIT_SUCCESS)
return error;
/* A missing value means true */
if (value == NULL) {
*out = 1;
return GIT_SUCCESS;
}
if (!strcasecmp(value, "true") ||
!strcasecmp(value, "yes") ||
!strcasecmp(value, "on")) {
*out = 1;
return GIT_SUCCESS;
}
if (!strcasecmp(value, "false") ||
!strcasecmp(value, "no") ||
!strcasecmp(value, "off")) {
*out = 0;
return GIT_SUCCESS;
}
/* Try to parse it as an integer */
error = git_config_get_int(cfg, name, out);
if (error == GIT_SUCCESS)
*out = !!(*out);
return error;
}
int git_config_get_string(git_config *cfg, const char *name, const char **out)
{
backend_internal *internal;
git_config_backend *backend;
assert(cfg->backends.length > 0);
internal = git_vector_get(&cfg->backends, 0);
backend = internal->backend;
return backend->get(backend, name, out);
}
#ifndef INCLUDE_config_h__
#define INCLUDE_config_h__
#include "git2.h"
#include "git2/config.h"
#include "vector.h"
struct git_config {
git_vector backends;
};
void git__strtolower(char *str);
void git__strntolower(char *str, int len);
#endif
......@@ -42,7 +42,7 @@ static struct {
{GIT_EOBJTYPE, "The specified object is of invalid type"},
{GIT_EOBJCORRUPTED, "The specified object has its data corrupted"},
{GIT_ENOTAREPO, "The specified repository is invalid"},
{GIT_EINVALIDTYPE, "The object type is invalid or doesn't match"},
{GIT_EINVALIDTYPE, "The object or config variable type is invalid or doesn't match"},
{GIT_EMISSINGOBJDATA, "The object cannot be written that because it's missing internal data"},
{GIT_EPACKCORRUPTED, "The packfile for the ODB is corrupted"},
{GIT_EFLOCKFAIL, "Failed to adquire or release a file lock"},
......
# This is a test
; of different comments
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
\ No newline at end of file
# This one checks for case sensitivity
[this "that"]
other = true
[this "That"]
other = yes
; This one tests for multiline values
[this "That"]
and = one one one \
two two \
three three
\ No newline at end of file
# A [section.subsection] header is case-insensitive
[section.SuBsection]
var = hello
# A variable name on its own is valid
[some.section]
variable
# Test for number suffixes
[number]
simple = 1
k = 1k
kk = 1K
m = 1m
mm = 1M
g = 1g
gg = 1G
/*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2,
* as published by the Free Software Foundation.
*
* In addition to the permissions in the GNU General Public License,
* the authors give you unlimited permission to link the compiled
* version of this file into combinations with other programs,
* and to distribute those combinations without any restriction
* coming from the use of this file. (The General Public License
* restrictions do apply in other respects; for example, they cover
* modification of the file, and distribution when not linked into
* a combined executable.)
*
* This file is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "test_lib.h"
#include "test_helpers.h"
#include <git2.h>
#define CONFIG_BASE TEST_RESOURCES "/config"
/*
* This one is so we know the code isn't completely broken
*/
BEGIN_TEST(config0, "read a simple configuration")
git_config *cfg;
int i;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config0"));
must_pass(git_config_get_int(cfg, "core.repositoryformatversion", &i));
must_be_true(i == 0);
must_pass(git_config_get_bool(cfg, "core.filemode", &i));
must_be_true(i == 1);
must_pass(git_config_get_bool(cfg, "core.bare", &i));
must_be_true(i == 0);
must_pass(git_config_get_bool(cfg, "core.logallrefupdates", &i));
must_be_true(i == 1);
git_config_free(cfg);
END_TEST
/*
* [this "that"] and [this "That] are different namespaces. Make sure
* each returns the correct one.
*/
BEGIN_TEST(config1, "case sensitivity")
git_config *cfg;
int i;
const char *str;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config1"));
must_pass(git_config_get_string(cfg, "this.that.other", &str));
must_be_true(!strcmp(str, "true"));
must_pass(git_config_get_string(cfg, "this.That.other", &str));
must_be_true(!strcmp(str, "yes"));
must_pass(git_config_get_bool(cfg, "this.that.other", &i));
must_be_true(i == 1);
must_pass(git_config_get_bool(cfg, "this.That.other", &i));
must_be_true(i == 1);
/* This one doesn't exist */
must_fail(git_config_get_bool(cfg, "this.thaT.other", &i));
git_config_free(cfg);
END_TEST
/*
* If \ is the last non-space character on the line, we read the next
* one, separating each line with SP.
*/
BEGIN_TEST(config2, "parse a multiline value")
git_config *cfg;
const char *str;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config2"));
must_pass(git_config_get_string(cfg, "this.That.and", &str));
must_be_true(!strcmp(str, "one one one two two three three"));
git_config_free(cfg);
END_TEST
/*
* This kind of subsection declaration is case-insensitive
*/
BEGIN_TEST(config3, "parse a [section.subsection] header")
git_config *cfg;
const char *str;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config3"));
must_pass(git_config_get_string(cfg, "section.subsection.var", &str));
must_be_true(!strcmp(str, "hello"));
/* Avoid a false positive */
str = "nohello";
must_pass(git_config_get_string(cfg, "section.subSectIon.var", &str));
must_be_true(!strcmp(str, "hello"));
git_config_free(cfg);
END_TEST
BEGIN_TEST(config4, "a variable name on its own is valid")
git_config *cfg;
const char *str;
int i;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config4"));
must_pass(git_config_get_string(cfg, "some.section.variable", &str));
must_be_true(str == NULL);
must_pass(git_config_get_bool(cfg, "some.section.variable", &i));
must_be_true(i == 1);
git_config_free(cfg);
END_TEST
BEGIN_TEST(config5, "test number suffixes")
git_config *cfg;
const char *str;
long int i;
must_pass(git_config_open_bare(&cfg, CONFIG_BASE "/config5"));
must_pass(git_config_get_long(cfg, "number.simple", &i));
must_be_true(i == 1);
must_pass(git_config_get_long(cfg, "number.k", &i));
must_be_true(i == 1 * 1024);
must_pass(git_config_get_long(cfg, "number.kk", &i));
must_be_true(i == 1 * 1024);
must_pass(git_config_get_long(cfg, "number.m", &i));
must_be_true(i == 1 * 1024 * 1024);
must_pass(git_config_get_long(cfg, "number.mm", &i));
must_be_true(i == 1 * 1024 * 1024);
must_pass(git_config_get_long(cfg, "number.g", &i));
must_be_true(i == 1 * 1024 * 1024 * 1024);
must_pass(git_config_get_long(cfg, "number.gg", &i));
must_be_true(i == 1 * 1024 * 1024 * 1024);
git_config_free(cfg);
END_TEST
BEGIN_SUITE(config)
ADD_TEST(config0);
ADD_TEST(config1);
ADD_TEST(config2);
ADD_TEST(config3);
ADD_TEST(config4);
ADD_TEST(config5);
END_SUITE
......@@ -44,6 +44,7 @@ DECLARE_SUITE(sqlite);
DECLARE_SUITE(hiredis);
DECLARE_SUITE(repository);
DECLARE_SUITE(threads);
DECLARE_SUITE(config);
static libgit2_suite suite_methods[]= {
SUITE_NAME(core),
......@@ -60,7 +61,8 @@ static libgit2_suite suite_methods[]= {
SUITE_NAME(sqlite),
SUITE_NAME(repository),
SUITE_NAME(threads),
SUITE_NAME(hiredis)
SUITE_NAME(hiredis),
SUITE_NAME(config),
};
#define GIT_SUITE_COUNT (ARRAY_SIZE(suite_methods))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment