Add message trailer iterator API

This also moves the existing callback-based API over to wrap the
new iterator API.
This commit is contained in:
Brian Lopez
2018-01-12 15:13:40 -08:00
parent 5734768b9e
commit d01759f5d1
3 changed files with 374 additions and 38 deletions

View File

@@ -58,6 +58,19 @@ typedef int(*git_message_trailer_cb)(const char *key, const char *value, void *p
*/
GIT_EXTERN(int) git_message_trailers(const char *message, git_message_trailer_cb cb, void *payload);
typedef struct git_message_trailer_iterator git_message_trailer_iterator;
GIT_EXTERN(int) git_message_trailer_iterator_new(
git_message_trailer_iterator **out,
const char *message);
GIT_EXTERN(int) git_message_trailer_iterator_next(
const char **key_out,
const char **value_out,
git_message_trailer_iterator *iter);
GIT_EXTERN(void) git_message_trailer_iterator_free(git_message_trailer_iterator *iter);
/** @} */
GIT_END_DECL

View File

@@ -248,7 +248,7 @@ static int find_trailer_end(const char *buf, size_t len)
return len - ignore_non_trailer(buf, len);
}
static char *find_trailer(const char *message, size_t* len)
static char *extract_trailer_block(const char *message, size_t* len)
{
size_t patch_start = find_patch_start(message);
size_t trailer_end = find_trailer_end(message, patch_start);
@@ -276,48 +276,84 @@ enum trailer_state {
S_IGNORE = 7,
};
#define NEXT(st) { state = (st); ptr++; continue; }
typedef struct {
char *trailer_block;
size_t block_len;
char *ptr;
} trailer_iterator;
int git_message_trailer_iterator_new(
git_message_trailer_iterator **out,
const char *message)
{
trailer_iterator *iter;
iter = git__calloc(1, sizeof(trailer_iterator));
GITERR_CHECK_ALLOC(iter);
iter->trailer_block = extract_trailer_block(message, &iter->block_len);
iter->ptr = iter->trailer_block;
*out = (git_message_trailer_iterator *) iter;
return 0;
}
void git_message_trailer_iterator_free(git_message_trailer_iterator *_iter)
{
trailer_iterator *iter = (trailer_iterator *) _iter;
if (iter == NULL)
return;
git__free(iter->trailer_block);
git__free(iter);
}
#define NEXT(st) { state = (st); iter->ptr++; continue; }
#define GOTO(st) { state = (st); continue; }
int git_message_trailers(const char *message, git_message_trailer_cb cb, void *payload)
int git_message_trailer_iterator_next(
const char **key_out,
const char **value_out,
git_message_trailer_iterator *_iter)
{
trailer_iterator *iter = (trailer_iterator *) _iter;
enum trailer_state state = S_START;
int rc = 0;
char *ptr;
char *key = NULL;
char *value = NULL;
size_t trailer_len;
char *trailer = find_trailer(message, &trailer_len);
if (*iter->ptr == 0) {
return GIT_ITEROVER;
}
for (ptr = trailer;;) {
while (iter->ptr != 0) {
switch (state) {
case S_START: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
goto ret;
}
key = ptr;
*key_out = iter->ptr;
GOTO(S_KEY);
}
case S_KEY: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
goto ret;
}
if (isalnum(*ptr) || *ptr == '-') {
if (isalnum(*iter->ptr) || *iter->ptr == '-') {
// legal key character
NEXT(S_KEY);
}
if (*ptr == ' ' || *ptr == '\t') {
if (*iter->ptr == ' ' || *iter->ptr == '\t') {
// optional whitespace before separator
*ptr = 0;
*iter->ptr = 0;
NEXT(S_KEY_WS);
}
if (strchr(TRAILER_SEPARATORS, *ptr)) {
*ptr = 0;
if (strchr(TRAILER_SEPARATORS, *iter->ptr)) {
*iter->ptr = 0;
NEXT(S_SEP_WS);
}
@@ -325,15 +361,15 @@ int git_message_trailers(const char *message, git_message_trailer_cb cb, void *p
GOTO(S_IGNORE);
}
case S_KEY_WS: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
goto ret;
}
if (*ptr == ' ' || *ptr == '\t') {
if (*iter->ptr == ' ' || *iter->ptr == '\t') {
NEXT(S_KEY_WS);
}
if (strchr(TRAILER_SEPARATORS, *ptr)) {
if (strchr(TRAILER_SEPARATORS, *iter->ptr)) {
NEXT(S_SEP_WS);
}
@@ -341,53 +377,46 @@ int git_message_trailers(const char *message, git_message_trailer_cb cb, void *p
GOTO(S_IGNORE);
}
case S_SEP_WS: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
goto ret;
}
if (*ptr == ' ' || *ptr == '\t') {
if (*iter->ptr == ' ' || *iter->ptr == '\t') {
NEXT(S_SEP_WS);
}
value = ptr;
*value_out = iter->ptr;
NEXT(S_VALUE);
}
case S_VALUE: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
GOTO(S_VALUE_END);
}
if (*ptr == '\n') {
if (*iter->ptr == '\n') {
NEXT(S_VALUE_NL);
}
NEXT(S_VALUE);
}
case S_VALUE_NL: {
if (*ptr == ' ') {
if (*iter->ptr == ' ') {
// continuation;
NEXT(S_VALUE);
}
ptr[-1] = 0;
iter->ptr[-1] = 0;
GOTO(S_VALUE_END);
}
case S_VALUE_END: {
if ((rc = cb(key, value, payload))) {
goto ret;
}
key = NULL;
value = NULL;
GOTO(S_START);
goto ret;
}
case S_IGNORE: {
if (*ptr == 0) {
if (*iter->ptr == 0) {
goto ret;
}
if (*ptr == '\n') {
if (*iter->ptr == '\n') {
NEXT(S_START);
}
@@ -397,6 +426,32 @@ int git_message_trailers(const char *message, git_message_trailer_cb cb, void *p
}
ret:
git__free(trailer);
return rc;
}
int git_message_trailers(const char *message, git_message_trailer_cb cb, void *payload)
{
git_message_trailer_iterator *iterator;
int rc;
if ((rc = git_message_trailer_iterator_new(&iterator, message))) {
return rc;
}
while (rc != GIT_ITEROVER) {
const char *key;
const char *value;
if ((rc = git_message_trailer_iterator_next(&key, &value, iterator))) {
goto ret;
}
if ((rc = cb(key, value, payload))) {
goto ret;
}
}
ret:
git_message_trailer_iterator_free(iterator);
return 0;
}

View File

@@ -177,3 +177,271 @@ void test_message_trailer__invalid(void)
"Another: trailer\n"
, trailers);
}
void test_message_trailer__iterator_simple(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Signed-off-by: foo@bar.com\n"
"Signed-off-by: someone@else.com\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Signed-off-by", key);
cl_assert_equal_s("foo@bar.com", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Signed-off-by", key);
cl_assert_equal_s("someone@else.com", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_no_whitespace(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Key:value\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Key", key);
cl_assert_equal_s("value", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_no_newline(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Key:value");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Key", key);
cl_assert_equal_s("value", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_not_last_paragraph(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Key: value\n"
"\n"
"More stuff\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_conflicts(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Key: value\n"
"\n"
"Conflicts:\n"
"\tfoo.c\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Key", key);
cl_assert_equal_s("value", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_patch(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Key: value\n"
"\n"
"---\n"
"More: stuff\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Key", key);
cl_assert_equal_s("value", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_continuation(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"A: b\n"
" c\n"
"D: e\n"
" f: g h\n"
"I: j\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("A", key);
cl_assert_equal_s("b\n c", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("D", key);
cl_assert_equal_s("e\n f: g h", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("I", key);
cl_assert_equal_s("j", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}
void test_message_trailer__iterator_invalid(void)
{
git_message_trailer_iterator *iterator;
const char *key;
const char *value;
int rc;
rc = git_message_trailer_iterator_new(
&iterator,
"Message\n"
"\n"
"Signed-off-by: some@one.com\n"
"Not a trailer\n"
"Another: trailer\n");
cl_assert(rc == 0);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Signed-off-by", key);
cl_assert_equal_s("some@one.com", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == 0);
cl_assert_equal_s("Another", key);
cl_assert_equal_s("trailer", value);
rc = git_message_trailer_iterator_next(&key, &value, iterator);
cl_assert(rc == GIT_ITEROVER);
git_message_trailer_iterator_free(iterator);
}