From ce23330fd636a99a25b3a6b7c81e63c424ae7d7c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:34:51 -0700 Subject: [PATCH 1/4] Add new git_signature_default API using config This adds a new API for creating a signature that uses the config to look up "user.name" and "user.email". --- include/git2/signature.h | 13 +++++++++++++ src/signature.c | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/include/git2/signature.h b/include/git2/signature.h index 00d19de66..2fa46d032 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -48,6 +48,19 @@ GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const c */ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); +/** + * Create a new action signature with default user and now timestamp. + * + * This looks up the user.name and user.email from the configuration and + * uses the current time as the timestamp, and creates a new signature + * based on that information. It will return GIT_ENOTFOUND if either the + * user.name or user.email are not set. + * + * @param out new signature + * @param repo repository pointer + * @return 0 on success, GIT_ENOTFOUND if config is missing, or error code + */ +GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo); /** * Create a copy of an existing signature. All internal strings are also diff --git a/src/signature.c b/src/signature.c index 0a34ccfaa..52ca2b375 100644 --- a/src/signature.c +++ b/src/signature.c @@ -74,7 +74,7 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema git_signature_free(p); return signature_error("Signature cannot have an empty name"); } - + p->when.time = time; p->when.offset = offset; @@ -129,6 +129,23 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema return 0; } +int git_signature_default(git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *user_name, *user_email; + + if ((error = git_repository_config(&cfg, repo)) < 0) + return error; + + if (!(error = git_config_get_string(&user_name, cfg, "user.name")) && + !(error = git_config_get_string(&user_email, cfg, "user.email"))) + error = git_signature_now(out, user_name, user_email); + + git_config_free(cfg); + return error; +} + int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { From 579d87c5d352232671dcfda43ec153883c01fc6f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:48:14 -0700 Subject: [PATCH 2/4] New test that inits repo and make commit --- tests-clar/repo/init.c | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 8cf73795f..02ea676fb 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -530,3 +530,52 @@ void test_repo_init__can_reinit_an_initialized_repository(void) git_repository_free(reinit); } + +void test_repo_init__init_with_initial_commit(void) +{ + git_index *index; + + cl_set_cleanup(&cleanup_repository, "committed"); + + /* Initialize the repository */ + cl_git_pass(git_repository_init(&_repo, "committed", 0)); + + /* Init will be automatically created when requested for a new repo */ + cl_git_pass(git_repository_index(&index, _repo)); + + /* Create a file so we can commit it + * + * If you are writing code outside the test suite, you can create this + * file any way that you like, such as: + * FILE *fp = fopen("committed/file.txt", "w"); + * fputs("some stuff\n", fp); + * fclose(fp); + * We like to use the help functions because they do error detection + * in a way that's easily compatible with our test suite. + */ + cl_git_mkfile("committed/file.txt", "some stuff\n"); + + /* Add file to the index */ + cl_git_pass(git_index_add_bypath(index, "file.txt")); + cl_git_pass(git_index_write(index)); + + /* Create a commit with the new contents of the index */ + { + git_signature *sig; + git_oid tree_id, commit_id; + git_tree *tree; + + cl_git_pass(git_signature_default(&sig, _repo)); + cl_git_pass(git_index_write_tree(&tree_id, index)); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + + cl_git_pass(git_commit_create_v( + &commit_id, _repo, "HEAD", sig, sig, + NULL, "First", tree, 0)); + + git_tree_free(tree); + git_signature_free(sig); + } + + git_index_free(index); +} From 944c1589c25e011537dd9162265cedeab363a103 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 14:49:38 -0700 Subject: [PATCH 3/4] Add example like "git init" --- examples/Makefile | 2 +- examples/init.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 examples/init.c diff --git a/examples/Makefile b/examples/Makefile index 95e46f0c6..d53ed8241 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -3,7 +3,7 @@ CC = gcc CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers LFLAGS = -L../build -lgit2 -lz -APPS = general showindex diff rev-list cat-file status log rev-parse +APPS = general showindex diff rev-list cat-file status log rev-parse init all: $(APPS) diff --git a/examples/init.c b/examples/init.c new file mode 100644 index 000000000..76b037a88 --- /dev/null +++ b/examples/init.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include +#include + +static void fail(const char *msg, const char *arg) +{ + if (arg) + fprintf(stderr, "%s %s\n", msg, arg); + else + fprintf(stderr, "%s\n", msg); + exit(1); +} + +static void usage(const char *error, const char *arg) +{ + fprintf(stderr, "error: %s '%s'\n", error, arg); + fprintf(stderr, "usage: init [-q | --quiet] [--bare] " + "[--template=] [--shared[=perms]] \n"); + exit(1); +} + +static size_t is_prefixed(const char *arg, const char *pfx) +{ + size_t len = strlen(pfx); + return !strncmp(arg, pfx, len) ? len : 0; +} + +static uint32_t parse_shared(const char *shared) +{ + if (!strcmp(shared, "false") || !strcmp(shared, "umask")) + return GIT_REPOSITORY_INIT_SHARED_UMASK; + + else if (!strcmp(shared, "true") || !strcmp(shared, "group")) + return GIT_REPOSITORY_INIT_SHARED_GROUP; + + else if (!strcmp(shared, "all") || !strcmp(shared, "world") || + !strcmp(shared, "everybody")) + return GIT_REPOSITORY_INIT_SHARED_ALL; + + else if (shared[0] == '0') { + long val; + char *end = NULL; + val = strtol(shared + 1, &end, 8); + if (end == shared + 1 || *end != 0) + usage("invalid octal value for --shared", shared); + return (uint32_t)val; + } + + else + usage("unknown value for --shared", shared); + + return 0; +} + +static void create_initial_commit(git_repository *repo); + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + int no_options = 1, quiet = 0, bare = 0, initial_commit = 0, i; + uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK; + const char *template = NULL, *gitdir = NULL, *dir = NULL; + size_t pfxlen; + + git_threads_init(); + + /* Process arguments */ + + for (i = 1; i < argc; ++i) { + char *a = argv[i]; + + if (a[0] == '-') + no_options = 0; + + if (a[0] != '-') { + if (dir != NULL) + usage("extra argument", a); + dir = a; + } + else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) + quiet = 1; + else if (!strcmp(a, "--bare")) + bare = 1; + else if ((pfxlen = is_prefixed(a, "--template=")) > 0) + template = a + pfxlen; + else if (!strcmp(a, "--separate-git-dir")) + gitdir = argv[++i]; + else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0) + gitdir = a + pfxlen; + else if (!strcmp(a, "--shared")) + shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if ((pfxlen = is_prefixed(a, "--shared=")) > 0) + shared = parse_shared(a + pfxlen); + else if (!strcmp(a, "--initial-commit")) + initial_commit = 1; + else + usage("unknown option", a); + } + + if (!dir) + usage("must specify directory to init", NULL); + + /* Initialize repository */ + + if (no_options) { + /* No options were specified, so let's demonstrate the default + * simple case of git_repository_init() API usage... + */ + + if (git_repository_init(&repo, dir, 0) < 0) + fail("Could not initialize repository", dir); + } + else { + /* Some command line options were specified, so we'll use the + * extended init API to handle them + */ + git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + + if (bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (template) { + opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + opts.template_path = template; + } + + if (gitdir) { + /* if you specified a separate git directory, then initialize + * the repository at that path and use the second path as the + * working directory of the repository (with a git-link file) + */ + opts.workdir_path = dir; + dir = gitdir; + } + + if (shared != 0) + opts.mode = shared; + + if (git_repository_init_ext(&repo, dir, &opts) < 0) + fail("Could not initialize repository", dir); + } + + if (!quiet) { + if (bare || gitdir) + dir = git_repository_path(repo); + else + dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", dir); + } + + /* As an extension to the basic "git init" command, this example + * gives the option to create an empty initial commit. This is + * mostly to demonstrate what it takes to do that, but also some + * people like to have that empty base commit in their repo. + */ + if (initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + git_threads_shutdown(); + + return 0; +} + +static void create_initial_commit(git_repository *repo) +{ + git_signature *sig; + git_index *index; + git_oid tree_id, commit_id; + git_tree *tree; + + /* First use the config to initialize a commit signature for the user */ + + if (git_signature_default(&sig, repo) < 0) + fail("Unable to create a commit signature.", + "Perhaps 'user.name' and 'user.email' are not set"); + + /* Now let's create an empty tree for this commit */ + + if (git_repository_index(&index, repo) < 0) + fail("Could not open repository index", NULL); + + /* Outside of this example, you could call git_index_add_bypath() + * here to put actual files into the index. For our purposes, we'll + * leave it empty for now. + */ + + if (git_index_write_tree(&tree_id, index) < 0) + fail("Unable to write initial tree from index", NULL); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fail("Could not look up initial tree", NULL); + + /* Ready to create the initial commit + * + * Normally creating a commit would involve looking up the current + * HEAD commit and making that be the parent of the initial commit, + * but here this is the first commit so there will be no parent. + */ + + if (git_commit_create_v( + &commit_id, repo, "HEAD", sig, sig, + NULL, "Initial commit", tree, 0) < 0) + fail("Could not create the initial commit", NULL); + + /* Clean up so we don't leak memory */ + + git_tree_free(tree); + git_signature_free(sig); +} From 0ea41445f4028460aefcd55cb37f10870ea9311c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 Aug 2013 15:03:15 -0700 Subject: [PATCH 4/4] Improve isolation of new test from user environs --- examples/init.c | 28 ++++++++++++++++++++++++++++ tests-clar/repo/init.c | 11 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/examples/init.c b/examples/init.c index 76b037a88..4a379c6e3 100644 --- a/examples/init.c +++ b/examples/init.c @@ -1,9 +1,27 @@ +/* + * This is a sample program that is similar to "git init". See the + * documentation for that (try "git help init") to understand what this + * program is emulating. + * + * This demonstrates using the libgit2 APIs to initialize a new repository. + * + * This also contains a special additional option that regular "git init" + * does not support which is "--initial-commit" to make a first empty commit. + * That is demonstrated in the "create_initial_commit" helper function. + * + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + #include #include #include #include #include +/* not actually good error handling */ static void fail(const char *msg, const char *arg) { if (arg) @@ -21,12 +39,14 @@ static void usage(const char *error, const char *arg) exit(1); } +/* simple string prefix test used in argument parsing */ static size_t is_prefixed(const char *arg, const char *pfx) { size_t len = strlen(pfx); return !strncmp(arg, pfx, len) ? len : 0; } +/* parse the tail of the --shared= argument */ static uint32_t parse_shared(const char *shared) { if (!strcmp(shared, "false") || !strcmp(shared, "umask")) @@ -54,8 +74,10 @@ static uint32_t parse_shared(const char *shared) return 0; } +/* forward declaration of helper to make an empty parent-less commit */ static void create_initial_commit(git_repository *repo); + int main(int argc, char *argv[]) { git_repository *repo = NULL; @@ -142,6 +164,8 @@ int main(int argc, char *argv[]) fail("Could not initialize repository", dir); } + /* Print a message to stdout like "git init" does */ + if (!quiet) { if (bare || gitdir) dir = git_repository_path(repo); @@ -167,6 +191,10 @@ int main(int argc, char *argv[]) return 0; } +/* Unlike regular "git init", this example shows how to create an initial + * empty commit in the repository. This is the helper function that does + * that. + */ static void create_initial_commit(git_repository *repo) { git_signature *sig; diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 02ea676fb..5076184b8 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -559,6 +559,17 @@ void test_repo_init__init_with_initial_commit(void) cl_git_pass(git_index_add_bypath(index, "file.txt")); cl_git_pass(git_index_write(index)); + /* Make sure we're ready to use git_signature_default :-) */ + { + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + cl_git_pass(git_config_set_string(local, "user.name", "Test User")); + cl_git_pass(git_config_set_string(local, "user.email", "t@example.com")); + git_config_free(local); + git_config_free(cfg); + } + /* Create a commit with the new contents of the index */ { git_signature *sig;