diff --git a/src/commit.c b/src/commit.c index d39fc540f..d8a72a98a 100644 --- a/src/commit.c +++ b/src/commit.c @@ -159,6 +159,7 @@ int commit_parse_buffer(git_commit *commit, void *data, size_t len, unsigned int return error; commit->commit_time = commit->committer->time; + commit->commit_timezone_offset = commit->committer->timezone_offset; /* parse commit message */ while (buffer <= buffer_end && *buffer == '\n') @@ -249,6 +250,19 @@ time_t git_commit_time(git_commit *commit) return commit->commit_time; } +int git_commit_timezone_offset(git_commit *commit) +{ + assert(commit); + + if (commit->commit_timezone_offset) + return commit->commit_timezone_offset; + + if (!commit->object.in_memory) + git_commit__parse_full(commit); + + return commit->commit_timezone_offset; +} + unsigned int git_commit_parentcount(git_commit *commit) { assert(commit); @@ -269,24 +283,24 @@ void git_commit_set_tree(git_commit *commit, git_tree *tree) commit->tree = tree; } -void git_commit_set_author(git_commit *commit, const char *name, const char *email, time_t time) +void git_commit_set_author(git_commit *commit, const char *name, const char *email, time_t time, int offset) { assert(commit && name && email); commit->object.modified = 1; CHECK_FULL_PARSE(); git_person__free(commit->author); - commit->author = git_person__new(name, email, time); + commit->author = git_person__new(name, email, time, offset); } -void git_commit_set_committer(git_commit *commit, const char *name, const char *email, time_t time) +void git_commit_set_committer(git_commit *commit, const char *name, const char *email, time_t time, int offset) { assert(commit && name && email); commit->object.modified = 1; CHECK_FULL_PARSE(); git_person__free(commit->committer); - commit->committer = git_person__new(name, email, time); + commit->committer = git_person__new(name, email, time, offset); commit->commit_time = time; } diff --git a/src/commit.h b/src/commit.h index eca566ddf..8c76c3b44 100644 --- a/src/commit.h +++ b/src/commit.h @@ -12,6 +12,8 @@ struct git_commit { git_object object; time_t commit_time; + int commit_timezone_offset; + git_vector parents; git_tree *tree; diff --git a/src/git2/commit.h b/src/git2/commit.h index 728679758..54b0ed872 100644 --- a/src/git2/commit.h +++ b/src/git2/commit.h @@ -92,6 +92,13 @@ GIT_EXTERN(const char *) git_commit_message(git_commit *commit); */ GIT_EXTERN(time_t) git_commit_time(git_commit *commit); +/** + * Get the commit timezone offset (i.e. committer's preferred timezone) of a commit. + * @param commit a previously loaded commit. + * @return positive or negative timezone offset, in minutes from UTC + */ +GIT_EXTERN(int) git_commit_timezone_offset(git_commit *commit); + /** * Get the committer of a commit. * @param commit a previously loaded commit. @@ -150,8 +157,9 @@ GIT_EXTERN(void) git_commit_set_message(git_commit *commit, const char *message) * @param name name of the new committer * @param email email of the new committer * @param time time when the committer committed the commit + * @param offset committer positive or negative timezone offset, in minutes from UTC */ -GIT_EXTERN(void) git_commit_set_committer(git_commit *commit, const char *name, const char *email, time_t time); +GIT_EXTERN(void) git_commit_set_committer(git_commit *commit, const char *name, const char *email, time_t time, int offset); /** * Set the author of a commit @@ -159,8 +167,9 @@ GIT_EXTERN(void) git_commit_set_committer(git_commit *commit, const char *name, * @param name name of the new author * @param email email of the new author * @param time time when the author created the commit + * @param offset author positive or negative timezone offset, in minutes from UTC */ -GIT_EXTERN(void) git_commit_set_author(git_commit *commit, const char *name, const char *email, time_t time); +GIT_EXTERN(void) git_commit_set_author(git_commit *commit, const char *name, const char *email, time_t time, int offset); /** * Set the tree which is pointed to by a commit diff --git a/src/git2/common.h b/src/git2/common.h index b819f0549..9734074ca 100644 --- a/src/git2/common.h +++ b/src/git2/common.h @@ -143,6 +143,7 @@ typedef struct git_person git_person; const char *git_person_name(git_person *person); const char *git_person_email(git_person *person); time_t git_person_time(git_person *person); +int git_person_timezone_offset(git_person *person); /** @} */ GIT_END_DECL diff --git a/src/git2/tag.h b/src/git2/tag.h index 39b45ac74..d78235e81 100644 --- a/src/git2/tag.h +++ b/src/git2/tag.h @@ -125,8 +125,9 @@ GIT_EXTERN(void) git_tag_set_name(git_tag *tag, const char *name); * @param name the name of the new tagger * @param email the email of the new tagger * @param time the time when the tag was created + * @param offset tagger positive or negative timezone offset, in minutes from UTC */ -GIT_EXTERN(void) git_tag_set_tagger(git_tag *tag, const char *name, const char *email, time_t time); +GIT_EXTERN(void) git_tag_set_tagger(git_tag *tag, const char *name, const char *email, time_t time, int offset); /** * Set the message of a tag diff --git a/src/person.c b/src/person.c index e93e1b827..96dc5d28d 100644 --- a/src/person.c +++ b/src/person.c @@ -38,7 +38,7 @@ void git_person__free(git_person *person) free(person); } -git_person *git_person__new(const char *name, const char *email, time_t time) +git_person *git_person__new(const char *name, const char *email, time_t time, int offset) { git_person *p; @@ -48,6 +48,7 @@ git_person *git_person__new(const char *name, const char *email, time_t time) p->name = git__strdup(name); p->email = git__strdup(email); p->time = time; + p->timezone_offset = offset; if (p->name == NULL || p->email == NULL) goto cleanup; @@ -74,6 +75,57 @@ time_t git_person_time(git_person *person) return person->time; } +int git_person_timezone_offset(git_person *person) +{ + return person->timezone_offset; +} + +int git_person__parse_timezone_offset(const char *buffer, int *offset_out) +{ + int offset, dec_offset; + int mins, hours; + + const char* offset_start; + char* offset_end; + + offset_start = buffer + 1; + + if (*offset_start == '\n') + { + *offset_out = 0; + return GIT_SUCCESS; + } + + if (offset_start[0] != '-' && offset_start[0] != '+') + return GIT_EOBJCORRUPTED; + + dec_offset = strtol(offset_start + 1, &offset_end, 10); + + if (offset_end - offset_start != 5) + return GIT_EOBJCORRUPTED; + + hours = dec_offset / 100; + mins = dec_offset % 100; + + if (hours > 14) // see http://www.worldtimezone.com/faq.html + return GIT_EOBJCORRUPTED; + + if (mins > 59) + return GIT_EOBJCORRUPTED; + + offset = (hours * 60) + mins; + + if (offset_start[0] == '-') + { + offset *= -1; + } + + *offset_out = offset; + + return GIT_SUCCESS; +} + + int git_person__parse(git_person *person, char **buffer_out, const char *buffer_end, const char *header) { @@ -82,6 +134,7 @@ int git_person__parse(git_person *person, char **buffer_out, int name_length, email_length; char *buffer = *buffer_out; char *line_end, *name_end, *email_end; + int offset = 0; memset(person, 0x0, sizeof(git_person)); @@ -128,13 +181,30 @@ int git_person__parse(git_person *person, char **buffer_out, if (person->time == 0) return GIT_EOBJCORRUPTED; + if (git_person__parse_timezone_offset(buffer, &offset) < GIT_SUCCESS) + return GIT_EOBJCORRUPTED; + + person->timezone_offset = offset; + *buffer_out = (line_end + 1); return GIT_SUCCESS; } int git_person__write(git_odb_source *src, const char *header, const git_person *person) { - return git__source_printf(src, "%s %s <%s> %u\n", header, person->name, person->email, person->time); + char *sign; + int offset, hours, mins; + + offset = person->timezone_offset; + sign = (person->timezone_offset < 0) ? "-" : "+"; + + if (offset < 0) + offset = -offset; + + hours = offset / 60; + mins = offset % 60; + + return git__source_printf(src, "%s %s <%s> %u %s%02d%02d\n", header, person->name, person->email, person->time, sign, hours, mins); } diff --git a/src/person.h b/src/person.h index 7a9fcb75b..d7ccbc9ba 100644 --- a/src/person.h +++ b/src/person.h @@ -10,10 +10,11 @@ struct git_person { char *name; /**< Full name */ char *email; /**< Email address */ time_t time; /**< Time when this person committed the change */ + int timezone_offset; /**< Time zone offset in minutes. Can be either positive or negative. */ }; void git_person__free(git_person *person); -git_person *git_person__new(const char *name, const char *email, time_t time); +git_person *git_person__new(const char *name, const char *email, time_t time, int offset); int git_person__parse(git_person *person, char **buffer_out, const char *buffer_end, const char *header); int git_person__write(git_odb_source *src, const char *header, const git_person *person); diff --git a/src/tag.c b/src/tag.c index b5b8b9fc3..7485daa0d 100644 --- a/src/tag.c +++ b/src/tag.c @@ -97,13 +97,13 @@ const git_person *git_tag_tagger(git_tag *t) return t->tagger; } -void git_tag_set_tagger(git_tag *tag, const char *name, const char *email, time_t time) +void git_tag_set_tagger(git_tag *tag, const char *name, const char *email, time_t time, int offset) { assert(tag && name && email); tag->object.modified = 1; git_person__free(tag->tagger); - tag->tagger = git_person__new(name, email, time); + tag->tagger = git_person__new(name, email, time, offset); } const char *git_tag_message(git_tag *t) diff --git a/tests/t0401-parse.c b/tests/t0401-parse.c index b82a9b168..5d3246321 100644 --- a/tests/t0401-parse.c +++ b/tests/t0401-parse.c @@ -131,7 +131,7 @@ END_TEST BEGIN_TEST(parse_person_test) -#define TEST_PERSON_PASS(_string, _header, _name, _email, _time) { \ +#define TEST_PERSON_PASS(_string, _header, _name, _email, _time, _offset) { \ char *ptr = _string; \ size_t len = strlen(_string);\ git_person person = {NULL, NULL, 0}; \ @@ -139,6 +139,7 @@ BEGIN_TEST(parse_person_test) must_be_true(strcmp(_name, person.name) == 0);\ must_be_true(strcmp(_email, person.email) == 0);\ must_be_true(_time == person.time);\ + must_be_true(_offset == person.timezone_offset);\ free(person.name); free(person.email);\ } @@ -151,25 +152,28 @@ BEGIN_TEST(parse_person_test) } TEST_PERSON_PASS( - "author Vicent Marti 12345 \n", - "author ", - "Vicent Marti", - "tanoku@gmail.com", - 12345); + "author Vicent Marti 12345 \n", + "author ", + "Vicent Marti", + "tanoku@gmail.com", + 12345, + 0); TEST_PERSON_PASS( "author Vicent Marti <> 12345 \n", "author ", "Vicent Marti", "", - 12345); + 12345, + 0); TEST_PERSON_PASS( - "author Vicent Marti 231301 +2020\n", + "author Vicent Marti 231301 +1020\n", "author ", "Vicent Marti", "tanoku@gmail.com", - 231301); + 231301, + 620); TEST_PERSON_PASS( "author Vicent Marti with an outrageously long name \ @@ -178,7 +182,8 @@ BEGIN_TEST(parse_person_test) "Vicent Marti with an outrageously long name \ which will probably overflow the buffer", "tanoku@gmail.com", - 12345); + 12345, + 0); TEST_PERSON_PASS( "author Vicent Marti 123456 +0000 \n", + "committer ", + "Vicent Marti", + "tanoku@gmail.com", + 123456, + 0); + + TEST_PERSON_PASS( + "committer Vicent Marti 123456 +0100 \n", + "committer ", + "Vicent Marti", + "tanoku@gmail.com", + 123456, + 60); + + TEST_PERSON_PASS( + "committer Vicent Marti 123456 -0100 \n", + "committer ", + "Vicent Marti", + "tanoku@gmail.com", + 123456, + -60); + + TEST_PERSON_FAIL( + "committer Vicent Marti 123456 -1500 \n", + "committer "); + + TEST_PERSON_FAIL( + "committer Vicent Marti 123456 +0163 \n", + "committer "); TEST_PERSON_FAIL( "author Vicent Marti 12345 \n", diff --git a/tests/t0403-write.c b/tests/t0403-write.c index 8b29b05ea..889c9e12f 100644 --- a/tests/t0403-write.c +++ b/tests/t0403-write.c @@ -42,8 +42,8 @@ BEGIN_TEST(writenew_test) git_commit_add_parent(commit, parent); /* Set other attributes */ - git_commit_set_committer(commit, COMMITTER_NAME, COMMITTER_EMAIL, 123456789); - git_commit_set_author(commit, COMMITTER_NAME, COMMITTER_EMAIL, 987654321); + git_commit_set_committer(commit, COMMITTER_NAME, COMMITTER_EMAIL, 123456789, 60); + git_commit_set_author(commit, COMMITTER_NAME, COMMITTER_EMAIL, 987654321, 90); git_commit_set_message(commit, COMMIT_MESSAGE); /* Check attributes were set correctly */ @@ -52,13 +52,16 @@ BEGIN_TEST(writenew_test) must_be_true(strcmp(author->name, COMMITTER_NAME) == 0); must_be_true(strcmp(author->email, COMMITTER_EMAIL) == 0); must_be_true(author->time == 987654321); + must_be_true(author->time_offset == 90); committer = git_commit_committer(commit); must_be_true(committer != NULL); must_be_true(strcmp(committer->name, COMMITTER_NAME) == 0); must_be_true(strcmp(committer->email, COMMITTER_EMAIL) == 0); must_be_true(committer->time == 123456789); + must_be_true(committer->time_offset == 60); must_be_true(git_commit_time(commit) == 123456789); + must_be_true(git_commit_time_offset(commit) == 60); must_be_true(strcmp(git_commit_message(commit), COMMIT_MESSAGE) == 0);