Commit 52f2390b by Vicent Marti

Add external API to access detailed commit attributes

The following new external methods have been added:

GIT_EXTERN(const char *) git_commit_message_short(git_commit *commit);
GIT_EXTERN(const char *) git_commit_message(git_commit *commit);
GIT_EXTERN(time_t) git_commit_time(git_commit *commit);
GIT_EXTERN(const git_commit_person *) git_commit_committer(git_commit *commit);
GIT_EXTERN(const git_commit_person *) git_commit_author(git_commit *commit);
GIT_EXTERN(const git_tree *) git_commit_tree(git_commit *commit);

A new structure, git_commit_person has been added to represent a
commit's author or committer.

The parsing of a commit has been split in two phases.
When adding a commit to the revision pool:
	- the commit's ODB object is opened
	- its raw contents are parsed for commit TIME, PARENTS and TREE
		(the minimal amount of data required to traverse the pool)
	- the commit's ODB object is closed

When querying for extended information on a commit:
	- the commit's ODB object is reopened
	- its raw contents are parsed for the requested information
	- the commit's ODB object remains open to handle additional queries

New unit tests have been added for the new functionality:

	In t0401-parse: parse_person_test
	In t0402-details: query_details_test

Signed-off-by: Vicent Marti <tanoku@gmail.com>
parent 225fe215
......@@ -29,14 +29,22 @@
#include "git/odb.h"
#define COMMIT_PRINT(commit) {\
char oid[41]; oid[40] = 0;\
git_oid_fmt(oid, &commit->object.id);\
printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\
char oid[41]; oid[40] = 0;\
git_oid_fmt(oid, &commit->object.id);\
printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\
}
void git_commit__free(git_commit *commit)
{
git_commit_list_clear(&commit->parents, 0);
if (commit->odb_open)
git_obj_close(&commit->odb_object);
free(commit->author);
free(commit->committer);
free(commit->message);
free(commit->message_short);
free(commit);
}
......@@ -69,7 +77,7 @@ git_commit *git_commit_parse(git_revpool *pool, const git_oid *id)
if ((commit = git_commit_lookup(pool, id)) == NULL)
return NULL;
if (git_commit_parse_existing(commit) < 0)
if (git_commit__parse_basic(commit) < 0)
goto error_cleanup;
return commit;
......@@ -79,30 +87,51 @@ error_cleanup:
return NULL;
}
int git_commit_parse_existing(git_commit *commit)
int git_commit__parse(git_commit *commit, unsigned int parse_flags, int close_db_object)
{
int error = 0;
git_obj commit_obj;
if (commit->parsed)
return 0;
if (!commit->odb_open) {
error = git_odb_read(&commit->odb_object, commit->object.pool->db, &commit->object.id);
if (error < 0)
return error;
error = git_odb_read(&commit_obj, commit->object.pool->db, &commit->object.id);
if (error < 0)
return error;
if (commit->odb_object.type != GIT_OBJ_COMMIT) {
git_obj_close(&commit->odb_object);
return GIT_EOBJTYPE;
}
if (commit_obj.type != GIT_OBJ_COMMIT) {
error = GIT_EOBJTYPE;
goto cleanup;
commit->odb_open = 1;
}
error = git_commit__parse_buffer(commit, commit_obj.data, commit_obj.len);
error = git_commit__parse_buffer(commit,
commit->odb_object.data, commit->odb_object.len, parse_flags);
if (close_db_object) {
git_obj_close(&commit->odb_object);
commit->odb_open = 0;
}
cleanup:
git_obj_close(&commit_obj);
return error;
}
int git_commit__parse_basic(git_commit *commit)
{
int error;
if (commit->basic_parse)
return 0;
error = git_commit__parse(commit,
(GIT_COMMIT_TREE | GIT_COMMIT_PARENTS | GIT_COMMIT_TIME), 1);
if (error < 0)
return error;
commit->basic_parse = 1;
return 0;
}
git_commit *git_commit_lookup(git_revpool *pool, const git_oid *id)
{
git_commit *commit = NULL;
......@@ -131,35 +160,67 @@ git_commit *git_commit_lookup(git_revpool *pool, const git_oid *id)
return commit;
}
int git_commit__parse_time(time_t *commit_time, char *buffer, const char *buffer_end)
int git_commit__parse_person(git_commit_person *person, char **buffer_out,
const char *buffer_end, const char *header)
{
if (memcmp(buffer, "author ", 7) != 0)
const size_t header_len = strlen(header);
int i;
char *buffer = *buffer_out;
char *line_end, *name, *email;
line_end = memchr(buffer, '\n', buffer_end - buffer);
if (!line_end)
return GIT_EOBJCORRUPTED;
buffer = memchr(buffer, '\n', buffer_end - buffer);
if (!buffer || ++buffer >= buffer_end)
if (buffer + (header_len + 1) > line_end)
return GIT_EOBJCORRUPTED;
if (memcmp(buffer, "committer ", 10) != 0)
if (memcmp(buffer, header, header_len) != 0)
return GIT_EOBJCORRUPTED;
buffer = memchr(buffer, '>', buffer_end - buffer);
if (!buffer || ++buffer >= buffer_end)
buffer += header_len;
/* Parse name field */
for (i = 0, name = person->name;
i < 64 && buffer < line_end && *buffer != '<';
++i)
*name++ = *buffer++;
*(name - 1) = 0;
while (buffer < line_end && *buffer != '<')
buffer++;
if (++buffer >= line_end)
return GIT_EOBJCORRUPTED;
*commit_time = strtol(buffer, &buffer, 10);
/* Parse email field */
for (i = 0, email = person->email;
i < 64 && buffer < line_end && *buffer != '>';
++i)
*email++ = *buffer++;
*email = 0;
while (buffer < line_end && *buffer != '>')
buffer++;
if (*commit_time == 0)
if (++buffer >= line_end)
return GIT_EOBJCORRUPTED;
buffer = memchr(buffer, '\n', buffer_end - buffer);
if (!buffer || ++buffer >= buffer_end)
person->time = strtol(buffer, &buffer, 10);
if (person->time == 0)
return GIT_EOBJCORRUPTED;
return (buffer < buffer_end) ? 0 : -1;
*buffer_out = (line_end + 1);
return 0;
}
int git_commit__parse_oid(git_oid *oid, char **buffer_out, const char *buffer_end, const char *header)
int git_commit__parse_oid(git_oid *oid, char **buffer_out,
const char *buffer_end, const char *header)
{
const size_t sha_len = GIT_OID_HEXSZ;
const size_t header_len = strlen(header);
......@@ -183,28 +244,33 @@ int git_commit__parse_oid(git_oid *oid, char **buffer_out, const char *buffer_en
return 0;
}
int git_commit__parse_buffer(git_commit *commit, void *data, size_t len)
int git_commit__parse_buffer(git_commit *commit, void *data, size_t len, unsigned int parse_flags)
{
char *buffer = (char *)data;
const char *buffer_end = (char *)data + len;
git_oid oid;
if (commit->parsed)
return 0;
git_commit_person person;
if (git_commit__parse_oid(&oid, &buffer, buffer_end, "tree ") < 0)
return GIT_EOBJCORRUPTED;
commit->tree = git_tree_lookup(commit->object.pool, &oid);
if (parse_flags & GIT_COMMIT_TREE)
commit->tree = git_tree_lookup(commit->object.pool, &oid);
/*
* TODO: commit grafts!
*/
if (parse_flags & GIT_COMMIT_PARENTS)
git_commit_list_clear(&commit->parents, 0);
while (git_commit__parse_oid(&oid, &buffer, buffer_end, "parent ") == 0) {
git_commit *parent;
if ((parse_flags & GIT_COMMIT_PARENTS) == 0)
continue;
if ((parent = git_commit_lookup(commit->object.pool, &oid)) == NULL)
return GIT_ENOTFOUND;
......@@ -216,14 +282,117 @@ int git_commit__parse_buffer(git_commit *commit, void *data, size_t len)
return GIT_ENOMEM;
}
if (git_commit__parse_time(&commit->commit_time, buffer, buffer_end) < 0)
if (git_commit__parse_person(&person, &buffer, buffer_end, "author ") < 0)
return GIT_EOBJCORRUPTED;
commit->parsed = 1;
if (parse_flags & GIT_COMMIT_AUTHOR) {
if (commit->author)
free(commit->author);
commit->author = git__malloc(sizeof(git_commit_person));
memcpy(commit->author, &person, sizeof(git_commit_person));
}
if (git_commit__parse_person(&person, &buffer, buffer_end, "committer ") < 0)
return GIT_EOBJCORRUPTED;
if (parse_flags & GIT_COMMIT_TIME)
commit->commit_time = person.time;
if (parse_flags & GIT_COMMIT_COMMITTER) {
if (commit->committer)
free(commit->committer);
commit->committer = git__malloc(sizeof(git_commit_person));
memcpy(commit->committer, &person, sizeof(git_commit_person));
}
/* parse commit message */
while (buffer <= buffer_end && *buffer == '\n')
buffer++;
if (buffer < buffer_end)
{
if (parse_flags & GIT_COMMIT_MESSAGE) {
size_t message_len = buffer_end - buffer;
commit->message = git__malloc(message_len + 1);
memcpy(commit->message, buffer, message_len);
commit->message[message_len] = 0;
}
if (parse_flags & GIT_COMMIT_MESSAGE_SHORT) {
char *line_end;
size_t message_len;
line_end = memchr(buffer, '\n', buffer_end - buffer);
message_len = line_end - buffer;
commit->message_short = git__malloc(message_len + 1);
memcpy(commit->message_short, buffer, message_len);
commit->message_short[message_len] = 0;
}
}
return 0;
}
const git_tree *git_commit_tree(git_commit *commit)
{
if (commit->tree)
return commit->tree;
git_commit__parse(commit, GIT_COMMIT_TREE, 0);
return commit->tree;
}
const git_commit_person *git_commit_author(git_commit *commit)
{
if (commit->author)
return commit->author;
git_commit__parse(commit, GIT_COMMIT_AUTHOR, 0);
return commit->author;
}
const git_commit_person *git_commit_committer(git_commit *commit)
{
if (commit->committer)
return commit->committer;
git_commit__parse(commit, GIT_COMMIT_COMMITTER, 0);
return commit->committer;
}
time_t git_commit_time(git_commit *commit)
{
if (commit->commit_time)
return commit->commit_time;
git_commit__parse(commit, GIT_COMMIT_TIME, 0);
return commit->commit_time;
}
const char *git_commit_message(git_commit *commit)
{
if (commit->message)
return commit->message;
git_commit__parse(commit, GIT_COMMIT_MESSAGE, 0);
return commit->message;
}
const char *git_commit_message_short(git_commit *commit)
{
if (commit->message_short)
return commit->message_short;
git_commit__parse(commit, GIT_COMMIT_MESSAGE_SHORT, 0);
return commit->message_short;
}
int git_commit_list_push_back(git_commit_list *list, git_commit *commit)
{
git_commit_node *node = NULL;
......
......@@ -23,31 +23,46 @@ struct git_commit_list {
typedef struct git_commit_list git_commit_list;
typedef struct git_commit_node git_commit_node;
#define GIT_COMMIT_TREE (1 << 1)
#define GIT_COMMIT_PARENTS (1 << 2)
#define GIT_COMMIT_AUTHOR (1 << 3)
#define GIT_COMMIT_COMMITTER (1 << 4)
#define GIT_COMMIT_TIME (1 << 5)
#define GIT_COMMIT_MESSAGE (1 << 6)
#define GIT_COMMIT_MESSAGE_SHORT (1 << 7)
#define GIT_COMMIT_FOOTERS (1 << 8)
struct git_commit {
git_revpool_object object;
git_obj odb_object;
time_t commit_time;
git_commit_list parents;
git_tree *tree;
git_commit_person *author;
git_commit_person *committer;
char *message;
char *message_short;
unsigned short in_degree;
unsigned parsed:1,
unsigned basic_parse:1,
odb_open:1,
seen:1,
uninteresting:1,
topo_delay:1,
flags:26;
flags:25;
};
void git_commit__free(git_commit *c);
int git_commit__parse(git_commit *commit, unsigned int flags, int close_odb);
int git_commit__parse_basic(git_commit *commit);
int git_commit__parse_oid(git_oid *oid, char **buffer_out, const char *buffer_end, const char *header);
int git_commit__parse_buffer(git_commit *commit, void *data, size_t len);
int git_commit__parse_time(time_t *commit_time, char *buffer, const char *buffer_end);
int git_commit__parse_buffer(git_commit *commit, void *data, size_t len, unsigned int parse_flags);
int git_commit__parse_person(git_commit_person *person, char **buffer_out, const char *buffer_end, const char *header);
void git_commit__mark_uninteresting(git_commit *commit);
int git_commit_parse_existing(git_commit *commit);
int git_commit_list_push_back(git_commit_list *list, git_commit *commit);
int git_commit_list_push_front(git_commit_list *list, git_commit *commit);
......
......@@ -3,6 +3,7 @@
#include "common.h"
#include "oid.h"
#include "tree.h"
/**
* @file git/commit.h
......@@ -16,6 +17,13 @@ GIT_BEGIN_DECL
/** Parsed representation of a commit object. */
typedef struct git_commit git_commit;
/** Parsed representation of an author/committer of a commit */
typedef struct git_commit_person {
char name[64]; /**< Full name */
char email[64]; /**< Email address */
time_t time; /**< Time when this person commited the change */
} git_commit_person;
/**
* Locate a reference to a commit without loading it.
* The generated commit object is owned by the revision
......@@ -45,11 +53,53 @@ GIT_EXTERN(git_commit *) git_commit_parse(git_revpool *pool, const git_oid *id);
/**
* Get the id of a commit.
* @param commit a previously parsed commit.
* @param commit a previously loaded commit.
* @return object identity for the commit.
*/
GIT_EXTERN(const git_oid *) git_commit_id(git_commit *commit);
/**
* Get the short (one line) message of a commit.
* @param commit a previously loaded commit.
* @return the short message of a commit
*/
GIT_EXTERN(const char *) git_commit_message_short(git_commit *commit);
/**
* Get the full message of a commit.
* @param commit a previously loaded commit.
* @return the message of a commit
*/
GIT_EXTERN(const char *) git_commit_message(git_commit *commit);
/**
* Get the commit time (i.e. committer time) of a commit.
* @param commit a previously loaded commit.
* @return the time of a commit
*/
GIT_EXTERN(time_t) git_commit_time(git_commit *commit);
/**
* Get the committer of a commit.
* @param commit a previously loaded commit.
* @return the committer of a commit
*/
GIT_EXTERN(const git_commit_person *) git_commit_committer(git_commit *commit);
/**
* Get the author of a commit.
* @param commit a previously loaded commit.
* @return the author of a commit
*/
GIT_EXTERN(const git_commit_person *) git_commit_author(git_commit *commit);
/**
* Get the tree pointed to by a commit.
* @param commit a previously loaded commit.
* @return the tree of a commit
*/
GIT_EXTERN(const git_tree *) git_commit_tree(git_commit *commit);
/** @} */
GIT_END_DECL
#endif
......@@ -92,8 +92,9 @@ int gitrp_push(git_revpool *pool, git_commit *commit)
if (commit->object.pool != pool || pool->walking)
return GIT_ERROR;
if (!commit->parsed) {
int error = git_commit_parse_existing(commit);
if (!commit->basic_parse) {
int error = git_commit__parse_basic(commit);
if (error < 0)
return error;
}
......@@ -129,8 +130,8 @@ int gitrp__enroot(git_revpool *pool, git_commit *commit)
if (commit->seen)
return 0;
if (commit->parsed == 0) {
error = git_commit_parse_existing(commit);
if (!commit->basic_parse) {
error = git_commit__parse_basic(commit);
if (error < 0)
return error;
}
......
#include "test_lib.h"
#include "test_helpers.h"
#include "commit.h"
#include <git/odb.h>
#include <git/commit.h>
#include <git/revwalk.h>
static const char *odb_dir = "../t0501-objects";
static const char *commit_ids[] = {
"a4a7dce85cf63874e984719f4fdd239f5145052f", /* 0 */
"9fd738e8f7967c078dceed8190330fc8648ee56a", /* 1 */
"4a202b346bb0fb0db7eff3cffeb3c70babbd2045", /* 2 */
"c47800c7266a2be04c571c04d5a6614691ea99bd", /* 3 */
"8496071c1b46c854b31185ea97743be6a8774479", /* 4 */
"5b5b025afb0b4c913b4c338a42934a3863bf3644", /* 5 */
};
BEGIN_TEST(query_details_test)
const size_t commit_count = sizeof(commit_ids) / sizeof(const char *);
unsigned int i;
git_odb *db;
git_revpool *pool;
must_pass(git_odb_open(&db, odb_dir));
pool = gitrp_alloc(db);
must_be_true(pool != NULL);
for (i = 0; i < commit_count; ++i) {
git_oid id;
git_commit *commit;
const git_commit_person *author, *committer;
const char *message, *message_short;
time_t commit_time;
git_oid_mkstr(&id, commit_ids[i]);
commit = git_commit_parse(pool, &id);
must_be_true(commit != NULL);
message = git_commit_message(commit);
message_short = git_commit_message_short(commit);
author = git_commit_author(commit);
committer = git_commit_committer(commit);
commit_time = git_commit_time(commit);
must_be_true(strcmp(author->name, "Scott Chacon") == 0);
must_be_true(strcmp(author->email, "schacon@gmail.com") == 0);
must_be_true(strcmp(committer->name, "Scott Chacon") == 0);
must_be_true(strcmp(committer->email, "schacon@gmail.com") == 0);
must_be_true(strchr(message, '\n') != NULL);
must_be_true(strchr(message_short, '\n') == NULL);
must_be_true(commit_time > 0);
}
gitrp_free(pool);
git_odb_close(db);
END_TEST
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