diff --git a/examples/commit.c b/examples/commit.c index aedc1af7e..1ba4739f0 100644 --- a/examples/commit.c +++ b/examples/commit.c @@ -39,7 +39,7 @@ int lg2_commit(git_repository *repo, int argc, char **argv) git_index *index; git_object *parent = NULL; git_reference *ref = NULL; - git_signature *signature; + git_signature *author_signature, *committer_signature; /* Validate args */ if (argc < 3 || strcmp(opt, "-m") != 0) { @@ -63,21 +63,25 @@ int lg2_commit(git_repository *repo, int argc, char **argv) check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "Error looking up tree", NULL); - check_lg2(git_signature_default(&signature, repo), "Error creating signature", NULL); + check_lg2(git_signature_default_author(&author_signature, repo), + "Error creating author signature", NULL); + check_lg2(git_signature_default_committer(&committer_signature, repo), + "Error creating committer signature", NULL); check_lg2(git_commit_create_v( &commit_oid, repo, "HEAD", - signature, - signature, + author_signature, + committer_signature, NULL, comment, tree, parent ? 1 : 0, parent), "Error creating commit", NULL); git_index_free(index); - git_signature_free(signature); + git_signature_free(author_signature); + git_signature_free(committer_signature); git_tree_free(tree); git_object_free(parent); git_reference_free(ref); diff --git a/examples/init.c b/examples/init.c index 2f681c5ae..4cd55abad 100644 --- a/examples/init.c +++ b/examples/init.c @@ -123,14 +123,15 @@ int lg2_init(git_repository *repo, int argc, char *argv[]) */ static void create_initial_commit(git_repository *repo) { - git_signature *sig; + git_signature *author_sig, *committer_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) + if ((git_signature_default_author(&author_sig, repo) < 0) || + (git_signature_default_committer(&committer_sig, repo) < 0)) fatal("Unable to create a commit signature.", "Perhaps 'user.name' and 'user.email' are not set"); @@ -162,14 +163,15 @@ static void create_initial_commit(git_repository *repo) */ if (git_commit_create_v( - &commit_id, repo, "HEAD", sig, sig, + &commit_id, repo, "HEAD", author_sig, committer_sig, NULL, "Initial commit", tree, 0) < 0) fatal("Could not create the initial commit", NULL); /** Clean up so we don't leak memory. */ git_tree_free(tree); - git_signature_free(sig); + git_signature_free(author_sig); + git_signature_free(committer_sig); } static void usage(const char *error, const char *arg) diff --git a/examples/stash.c b/examples/stash.c index 8142439c7..c330cbce1 100644 --- a/examples/stash.c +++ b/examples/stash.c @@ -108,7 +108,7 @@ static int cmd_push(git_repository *repo, struct opts *opts) if (opts->argc) usage("push does not accept any parameters"); - check_lg2(git_signature_default(&signature, repo), + check_lg2(git_signature_default_author(&signature, repo), "Unable to get signature", NULL); check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT), "Unable to save stash", NULL); diff --git a/examples/tag.c b/examples/tag.c index e4f71ae62..9bebcd1e6 100644 --- a/examples/tag.c +++ b/examples/tag.c @@ -226,7 +226,7 @@ static void action_create_tag(tag_state *state) check_lg2(git_revparse_single(&target, repo, opts->target), "Unable to resolve spec", opts->target); - check_lg2(git_signature_default(&tagger, repo), + check_lg2(git_signature_default_author(&tagger, repo), "Unable to create signature", NULL); check_lg2(git_tag_create(&oid, repo, opts->tag_name, diff --git a/include/git2/signature.h b/include/git2/signature.h index 849998e66..31aa9676d 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -48,6 +48,52 @@ 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 author action signature with default information based on the + * configuration and environment variables. + * + * If GIT_AUTHOR_NAME environment variable is set it uses that over the + * user.name value from the configuration. + * + * If GIT_AUTHOR_EMAIL environment variable is set it uses that over the + * user.email value from the configuration. The EMAIL environment variable is + * the fallback email address in case the user.email configuration value isn't + * set. + * + * If GIT_AUTHOR_DATE is set it uses that, otherwise it uses the current time + * as the timestamp. + * + * It will return GIT_ENOTFOUND if either the user.name or user.email are not + * set and there is no fallback from an environment variable. + * + * @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_author(git_signature **out, git_repository *repo); + +/** Create a new committer action signature with default information based on + * the configuration and environment variables. + * + * If GIT_COMMITTER_NAME environment variable is set it uses that over the + * user.name value from the configuration. + * + * If GIT_COMMITTER_EMAIL environment variable is set it uses that over the + * user.email value from the configuration. The EMAIL environment variable is + * the fallback email address in case the user.email configuration value isn't + * set. + * + * If GIT_COMMITTER_DATE is set it uses that, otherwise it uses the current + * time as the timestamp. + * + * It will return GIT_ENOTFOUND if either the user.name or user.email are not + * set and there is no fallback from an environment variable. + * + * @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_committer(git_signature **out, git_repository *repo); + +#ifndef GIT_DEPRECATE_HARD /** * Create a new action signature with default user and now timestamp. * @@ -56,11 +102,13 @@ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const c * based on that information. It will return GIT_ENOTFOUND if either the * user.name or user.email are not set. * + * @deprecated use git_signature_default_author or git_signature_default_committer instead * @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); +#endif /** * Create a new signature by parsing the given buffer, which is diff --git a/src/libgit2/rebase.c b/src/libgit2/rebase.c index 77e442e98..e9de62652 100644 --- a/src/libgit2/rebase.c +++ b/src/libgit2/rebase.c @@ -1268,7 +1268,7 @@ static int rebase_copy_note( } if (!committer) { - if((error = git_signature_default(&who, rebase->repo)) < 0) { + if((error = git_signature_default_committer(&who, rebase->repo)) < 0) { if (error != GIT_ENOTFOUND || (error = git_signature_now(&who, "unknown", "unknown")) < 0) goto done; diff --git a/src/libgit2/refs.c b/src/libgit2/refs.c index c1ed04d23..8b553d40a 100644 --- a/src/libgit2/refs.c +++ b/src/libgit2/refs.c @@ -451,7 +451,7 @@ int git_reference__log_signature(git_signature **out, git_repository *repo) git_signature *who; if(((error = refs_configured_ident(&who, repo)) < 0) && - ((error = git_signature_default(&who, repo)) < 0) && + ((error = git_signature_default_author(&who, repo)) < 0) && ((error = git_signature_now(&who, "unknown", "unknown")) < 0)) return error; diff --git a/src/libgit2/signature.c b/src/libgit2/signature.c index 12d2b5f8d..1a694c4cf 100644 --- a/src/libgit2/signature.c +++ b/src/libgit2/signature.c @@ -10,6 +10,7 @@ #include "repository.h" #include "git2/common.h" #include "posix.h" +#include "date.h" void git_signature_free(git_signature *sig) { @@ -201,6 +202,78 @@ int git_signature_default(git_signature **out, git_repository *repo) return error; } +int git_signature__default_from_env(const char *name_env_var, const char *email_env_var, + const char *date_env_var, git_signature **out, git_repository *repo) +{ + int error; + git_config *cfg; + const char *name, *email, *date; + git_time_t timestamp; + int offset; + git_str name_env = GIT_STR_INIT; + git_str email_env = GIT_STR_INIT; + git_str date_env = GIT_STR_INIT; + int have_email_env = -1; + + if ((error = git_repository_config_snapshot(&cfg, repo)) < 0) + return error; + + /* Check if the environment variable for the name is set */ + if (!(git__getenv(&name_env, name_env_var))) + name = git_str_cstr(&name_env); + else + /* or else read the configuration value. */ + if ((error = git_config_get_string(&name, cfg, "user.name")) < 0) + goto done; + + /* Check if the environment variable for the email is set. */ + if (!(git__getenv(&email_env, email_env_var))) + email = git_str_cstr(&email_env); + else { + /* Check if the fallback EMAIL environment variable is set + * before we check the configuration so that we preserve the + * error message if the configuration value is missing. */ + git_str_dispose(&email_env); + have_email_env = !git__getenv(&email_env, "EMAIL"); + if ((error = git_config_get_string(&email, cfg, "user.email")) < 0) { + if (have_email_env) { + email = git_str_cstr(&email_env); + error = 0; + } else + goto done; + } + } + + /* Check if the environment variable for the timestamp is set */ + if (!(git__getenv(&date_env, date_env_var))) { + date = git_str_cstr(&date_env); + if ((error = git_date_offset_parse(×tamp, &offset, date)) < 0) + goto done; + error = git_signature_new(out, name, email, timestamp, offset); + } else + /* or else default to the current timestamp. */ + error = git_signature_now(out, name, email); + +done: + git_config_free(cfg); + git_str_dispose(&name_env); + git_str_dispose(&email_env); + git_str_dispose(&date_env); + return error; +} + +int git_signature_default_author(git_signature **out, git_repository *repo) +{ + return git_signature__default_from_env("GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", + "GIT_AUTHOR_DATE", out, repo); +} + +int git_signature_default_committer(git_signature **out, git_repository *repo) +{ + return git_signature__default_from_env("GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", + "GIT_COMMITTER_DATE", out, repo); +} + int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { diff --git a/src/libgit2/signature.h b/src/libgit2/signature.h index 5c8270954..23356161e 100644 --- a/src/libgit2/signature.h +++ b/src/libgit2/signature.h @@ -17,7 +17,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender); void git_signature__writebuf(git_str *buf, const char *header, const git_signature *sig); bool git_signature__equal(const git_signature *one, const git_signature *two); - int git_signature__pdup(git_signature **dest, const git_signature *source, git_pool *pool); +int git_signature__default_from_env(const char *name_env_var, const char *email_env_var, const char *date_env_var, git_signature **out, git_repository *repo); #endif diff --git a/src/util/date.c b/src/util/date.c index 4d757e21a..161712e16 100644 --- a/src/util/date.c +++ b/src/util/date.c @@ -858,7 +858,7 @@ static git_time_t approxidate_str(const char *date, return update_tm(&tm, &now, 0); } -int git_date_parse(git_time_t *out, const char *date) +int git_date_offset_parse(git_time_t *out, int * out_offset, const char *date) { time_t time_sec; git_time_t timestamp; @@ -866,6 +866,7 @@ int git_date_parse(git_time_t *out, const char *date) if (!parse_date_basic(date, ×tamp, &offset)) { *out = timestamp; + *out_offset = offset; return 0; } @@ -876,6 +877,13 @@ int git_date_parse(git_time_t *out, const char *date) return error_ret; } +int git_date_parse(git_time_t *out, const char *date) +{ + int offset; + + return git_date_offset_parse(out, &offset, date); +} + int git_date_rfc2822_fmt(git_str *out, git_time_t time, int offset) { time_t t; diff --git a/src/util/date.h b/src/util/date.h index 7ebd3c30e..785fc064b 100644 --- a/src/util/date.h +++ b/src/util/date.h @@ -10,9 +10,21 @@ #include "util.h" #include "str.h" +/* + * Parse a string into a value as a git_time_t with a timezone offset. + * + * Sample valid input: + * - "yesterday" + * - "July 17, 2003" + * - "2003-7-17 08:23i+03" + */ +extern int git_date_offset_parse(git_time_t *out, int *out_offset, const char *date); + /* * Parse a string into a value as a git_time_t. * + * Timezone offset is ignored. + * * Sample valid input: * - "yesterday" * - "July 17, 2003" diff --git a/tests/libgit2/commit/signature.c b/tests/libgit2/commit/signature.c index fddd5076e..b41182ce6 100644 --- a/tests/libgit2/commit/signature.c +++ b/tests/libgit2/commit/signature.c @@ -153,3 +153,83 @@ void test_commit_signature__pos_and_neg_zero_offsets_dont_match(void) git_signature_free((git_signature *)with_neg_zero); git_signature_free((git_signature *)with_pos_zero); } + +static git_repository *g_repo; + +void test_commit_signature__initialize(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); +} + +void test_commit_signature__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +void test_commit_signature__signature_default(void) +{ + git_signature *author_sign, *committer_sign; + git_config *cfg, *local; + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_open_level(&local, cfg, GIT_CONFIG_LEVEL_LOCAL)); + /* No configuration value is set and no environment variable */ + cl_git_fail(git_signature_default_author(&author_sign, g_repo)); + cl_git_fail(git_signature_default_committer(&committer_sign, g_repo)); + /* Name is read from configuration and email is read from fallback EMAIL + * environment variable */ + cl_git_pass(git_config_set_string(local, "user.name", "Name (config)")); + cl_setenv("EMAIL", "email-envvar@example.com"); + cl_git_pass(git_signature_default_author(&author_sign, g_repo)); + cl_git_pass(git_signature_default_committer(&committer_sign, g_repo)); + cl_assert_equal_s("Name (config)", author_sign->name); + cl_assert_equal_s("email-envvar@example.com", author_sign->email); + cl_assert_equal_s("Name (config)", committer_sign->name); + cl_assert_equal_s("email-envvar@example.com", committer_sign->email); + cl_setenv("EMAIL", NULL); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* Environment variables have precedence over configuration */ + cl_git_pass(git_config_set_string(local, "user.email", "config@example.com")); + cl_setenv("GIT_AUTHOR_NAME", "Author (envvar)"); + cl_setenv("GIT_AUTHOR_EMAIL", "author-envvar@example.com"); + cl_setenv("GIT_COMMITTER_NAME", "Committer (envvar)"); + cl_setenv("GIT_COMMITTER_EMAIL", "committer-envvar@example.com"); + cl_git_pass(git_signature_default_author(&author_sign, g_repo)); + cl_git_pass(git_signature_default_committer(&committer_sign, g_repo)); + cl_assert_equal_s("Author (envvar)", author_sign->name); + cl_assert_equal_s("author-envvar@example.com", author_sign->email); + cl_assert_equal_s("Committer (envvar)", committer_sign->name); + cl_assert_equal_s("committer-envvar@example.com", committer_sign->email); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* When environment variables are not set we can still read from + * configuration */ + cl_setenv("GIT_AUTHOR_NAME", NULL); + cl_setenv("GIT_AUTHOR_EMAIL", NULL); + cl_setenv("GIT_COMMITTER_NAME", NULL); + cl_setenv("GIT_COMMITTER_EMAIL", NULL); + cl_git_pass(git_signature_default_author(&author_sign, g_repo)); + cl_git_pass(git_signature_default_committer(&committer_sign, g_repo)); + cl_assert_equal_s("Name (config)", author_sign->name); + cl_assert_equal_s("config@example.com", author_sign->email); + cl_assert_equal_s("Name (config)", committer_sign->name); + cl_assert_equal_s("config@example.com", committer_sign->email); + git_signature_free(author_sign); + git_signature_free(committer_sign); + /* We can also override the timestamp with an environment variable */ + cl_setenv("GIT_AUTHOR_DATE", "1971-02-03 04:05:06+01"); + cl_setenv("GIT_COMMITTER_DATE", "1988-09-10 11:12:13-01"); + cl_git_pass(git_signature_default_author(&author_sign, g_repo)); + cl_git_pass(git_signature_default_committer(&committer_sign, g_repo)); + cl_assert_equal_i(34398306, author_sign->when.time); /* 1971-02-03 03:05:06 UTC */ + cl_assert_equal_i(60, author_sign->when.offset); + cl_assert_equal_i(589896733, committer_sign->when.time); /* 1988-09-10 12:12:13 UTC */ + cl_assert_equal_i(-60, committer_sign->when.offset); + git_signature_free(author_sign); + git_signature_free(committer_sign); + cl_setenv("GIT_AUTHOR_DATE", NULL); + cl_setenv("GIT_COMMITTER_DATE", NULL); + git_config_free(local); + git_config_free(cfg); +} diff --git a/tests/libgit2/date/date.c b/tests/libgit2/date/date.c index 82b5c6728..b5796861c 100644 --- a/tests/libgit2/date/date.c +++ b/tests/libgit2/date/date.c @@ -20,3 +20,11 @@ void test_date_date__invalid_date(void) cl_git_fail(git_date_parse(&d, "")); cl_git_fail(git_date_parse(&d, "NEITHER_INTEGER_NOR_DATETIME")); } + +void test_date_date__offset(void) +{ + git_time_t d; + int offset; + cl_git_pass(git_date_offset_parse(&d, &offset, "1970-1-1 01:00:00+03")); + cl_assert_equal_i(offset, 3*60); +} diff --git a/tests/libgit2/remote/fetch.c b/tests/libgit2/remote/fetch.c index a5d3272c5..c24ec5a01 100644 --- a/tests/libgit2/remote/fetch.c +++ b/tests/libgit2/remote/fetch.c @@ -82,7 +82,7 @@ static void do_time_travelling_fetch(git_oid *commit1id, git_oid *commit2id, cl_git_pass(git_treebuilder_new(&tb, repo1, NULL)); cl_git_pass(git_treebuilder_write(&empty_tree_id, tb)); cl_git_pass(git_tree_lookup(&empty_tree, repo1, &empty_tree_id)); - cl_git_pass(git_signature_default(&sig, repo1)); + cl_git_pass(git_signature_default_author(&sig, repo1)); cl_git_pass(git_commit_create(commit1id, repo1, REPO1_REFNAME, sig, sig, NULL, "one", empty_tree, 0, NULL)); cl_git_pass(git_commit_lookup(&commit1, repo1, commit1id)); diff --git a/tests/libgit2/repo/init.c b/tests/libgit2/repo/init.c index d78ec063c..bb26e5443 100644 --- a/tests/libgit2/repo/init.c +++ b/tests/libgit2/repo/init.c @@ -581,7 +581,7 @@ void test_repo_init__init_with_initial_commit(void) * made to a repository... */ - /* Make sure we're ready to use git_signature_default :-) */ + /* Make sure we're ready to use git_signature_default_author :-) */ { git_config *cfg, *local; cl_git_pass(git_repository_config(&cfg, g_repo)); @@ -594,20 +594,22 @@ void test_repo_init__init_with_initial_commit(void) /* Create a commit with the new contents of the index */ { - git_signature *sig; + git_signature *author_sig, *committer_sig; git_oid tree_id, commit_id; git_tree *tree; - cl_git_pass(git_signature_default(&sig, g_repo)); + cl_git_pass(git_signature_default_author(&author_sig, g_repo)); + cl_git_pass(git_signature_default_committer(&committer_sig, g_repo)); cl_git_pass(git_index_write_tree(&tree_id, index)); cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); cl_git_pass(git_commit_create_v( - &commit_id, g_repo, "HEAD", sig, sig, + &commit_id, g_repo, "HEAD", author_sig, committer_sig, NULL, "First", tree, 0)); git_tree_free(tree); - git_signature_free(sig); + git_signature_free(author_sig); + git_signature_free(committer_sig); } git_index_free(index);