From 7f989007737a559d6523b738fe788314118b1cf9 Mon Sep 17 00:00:00 2001 From: Adam Samalik Date: Thu, 29 Jun 2023 18:25:29 +0200 Subject: [PATCH] re-import sources as agreed with the maintainer --- .gitignore | 5 +- tests/scripts/Makefile | 12 + tests/scripts/common.c | 237 ++++++++++++++++++ tests/scripts/common.h | 105 ++++++++ tests/scripts/init.c | 253 ++++++++++++++++++++ tests/scripts/log.c | 479 +++++++++++++++++++++++++++++++++++++ tests/scripts/run_tests.sh | 16 ++ tests/tests.yml | 18 ++ 8 files changed, 1124 insertions(+), 1 deletion(-) create mode 100644 tests/scripts/Makefile create mode 100644 tests/scripts/common.c create mode 100644 tests/scripts/common.h create mode 100644 tests/scripts/init.c create mode 100644 tests/scripts/log.c create mode 100755 tests/scripts/run_tests.sh create mode 100644 tests/tests.yml diff --git a/.gitignore b/.gitignore index 3fc4214..357e509 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ -SOURCES/libgit2-0.26.8.tar.gz +/libgit2-0.26.3.tar.gz +/libgit2-0.26.4.tar.gz +/libgit2-0.26.5.tar.gz +/libgit2-0.26.6.tar.gz /libgit2-0.26.8.tar.gz diff --git a/tests/scripts/Makefile b/tests/scripts/Makefile new file mode 100644 index 0000000..bb229e4 --- /dev/null +++ b/tests/scripts/Makefile @@ -0,0 +1,12 @@ +.PHONY: all + +CC = gcc +CFLAGS = -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers +LFLAGS = -lgit2 -lz +APPS = init log + +all: $(APPS) + +% : %.c + $(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS) + diff --git a/tests/scripts/common.c b/tests/scripts/common.c new file mode 100644 index 0000000..96f5eaa --- /dev/null +++ b/tests/scripts/common.c @@ -0,0 +1,237 @@ +/* + * Utilities library for libgit2 examples + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +void check_lg2(int error, const char *message, const char *extra) +{ + const git_error *lg2err; + const char *lg2msg = "", *lg2spacer = ""; + + if (!error) + return; + + if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) { + lg2msg = lg2err->message; + lg2spacer = " - "; + } + + if (extra) + fprintf(stderr, "%s '%s' [%d]%s%s\n", + message, extra, error, lg2spacer, lg2msg); + else + fprintf(stderr, "%s [%d]%s%s\n", + message, error, lg2spacer, lg2msg); + + exit(1); +} + +void fatal(const char *message, const char *extra) +{ + if (extra) + fprintf(stderr, "%s %s\n", message, extra); + else + fprintf(stderr, "%s\n", message); + + exit(1); +} + +size_t is_prefixed(const char *str, const char *pfx) +{ + size_t len = strlen(pfx); + return strncmp(str, pfx, len) ? 0 : len; +} + +int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) { + *out = def; + return 1; + } + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +int match_str_arg( + const char **out, struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return 0; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected value following argument", opt); + args->pos += 1; + *out = args->argv[args->pos]; + return 1; + } + + if (found[len] == '=') { + *out = found + len + 1; + return 1; + } + + return 0; +} + +static const char *match_numeric_arg(struct args_info *args, const char *opt) +{ + const char *found = args->argv[args->pos]; + size_t len = is_prefixed(found, opt); + + if (!len) + return NULL; + + if (!found[len]) { + if (args->pos + 1 == args->argc) + fatal("expected numeric value following argument", opt); + args->pos += 1; + found = args->argv[args->pos]; + } else { + found = found + len; + if (*found == '=') + found++; + } + + return found; +} + +int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint16_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint32_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + +static int match_int_internal( + int *out, const char *str, int allow_negative, const char *opt) +{ + char *endptr = NULL; + int val = (int)strtol(str, &endptr, 10); + + if (!endptr || *endptr != '\0') + fatal("expected number", opt); + else if (val < 0 && !allow_negative) + fatal("negative values are not allowed", opt); + + if (out) + *out = val; + + return 1; +} + +int is_integer(int *out, const char *str, int allow_negative) +{ + return match_int_internal(out, str, allow_negative, NULL); +} + +int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative) +{ + const char *found = match_numeric_arg(args, opt); + if (!found) + return 0; + return match_int_internal(out, found, allow_negative, opt); +} + +int diff_output( + const git_diff_delta *d, + const git_diff_hunk *h, + const git_diff_line *l, + void *p) +{ + FILE *fp = (FILE*)p; + + (void)d; (void)h; + + if (!fp) + fp = stdout; + + if (l->origin == GIT_DIFF_LINE_CONTEXT || + l->origin == GIT_DIFF_LINE_ADDITION || + l->origin == GIT_DIFF_LINE_DELETION) + fputc(l->origin, fp); + + fwrite(l->content, 1, l->content_len, fp); + + return 0; +} + +void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish) +{ + git_object *obj = NULL; + + check_lg2( + git_revparse_single(&obj, repo, treeish), + "looking up object", treeish); + + check_lg2( + git_object_peel((git_object **)out, obj, GIT_OBJ_TREE), + "resolving object to tree", treeish); + + git_object_free(obj); +} + diff --git a/tests/scripts/common.h b/tests/scripts/common.h new file mode 100644 index 0000000..adea0d3 --- /dev/null +++ b/tests/scripts/common.h @@ -0,0 +1,105 @@ +/* + * Utilities library for libgit2 examples + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include +#include +#include +#include + +/** + * Check libgit2 error code, printing error to stderr on failure and + * exiting the program. + */ +extern void check_lg2(int error, const char *message, const char *extra); + +/** + * Exit the program, printing error to stderr + */ +extern void fatal(const char *message, const char *extra); + +/** + * Check if a string has the given prefix. Returns 0 if not prefixed + * or the length of the prefix if it is. + */ +extern size_t is_prefixed(const char *str, const char *pfx); + +/** + * Match an integer string, returning 1 if matched, 0 if not. + */ +extern int is_integer(int *out, const char *str, int allow_negative); + +struct args_info { + int argc; + char **argv; + int pos; +}; +#define ARGS_INFO_INIT { argc, argv, 0 } + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; if value not supplied, + * default value `def` will be given. otherwise return 0. + */ +extern int optional_str_arg( + const char **out, struct args_info *args, const char *opt, const char *def); + +/** + * Check current `args` entry against `opt` string. If it matches + * exactly, take the next arg as a string; if it matches as a prefix with + * an equal sign, take the remainder as a string; otherwise return 0. + */ +extern int match_str_arg( + const char **out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint16. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint16_t value; otherwise return 0. + */ +extern int match_uint16_arg( + uint16_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as uint32. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint32_t value; otherwise return 0. + */ +extern int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt); + +/** + * Check current `args` entry against `opt` string parsing as int. If + * `opt` matches exactly, take the next arg as an int value; if it matches + * as a prefix (equal sign optional), take the remainder of the arg as a + * int value; otherwise return 0. + */ +extern int match_int_arg( + int *out, struct args_info *args, const char *opt, int allow_negative); + +/** + * Basic output function for plain text diff output + * Pass `FILE*` such as `stdout` or `stderr` as payload (or NULL == `stdout`) + */ +extern int diff_output( + const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*); + +/** + * Convert a treeish argument to an actual tree; this will call check_lg2 + * and exit the program if `treeish` cannot be resolved to a tree + */ +extern void treeish_to_tree( + git_tree **out, git_repository *repo, const char *treeish); diff --git a/tests/scripts/init.c b/tests/scripts/init.c new file mode 100644 index 0000000..fe7a672 --- /dev/null +++ b/tests/scripts/init.c @@ -0,0 +1,253 @@ +/* + * libgit2 "init" example - shows how to initialize a new repo + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** + * 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. + */ + +/** Forward declarations of helpers */ +struct opts { + int no_options; + int quiet; + int bare; + int initial_commit; + uint32_t shared; + const char *template; + const char *gitdir; + const char *dir; +}; +static void create_initial_commit(git_repository *repo); +static void parse_opts(struct opts *o, int argc, char *argv[]); + + +int main(int argc, char *argv[]) +{ + git_repository *repo = NULL; + struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 }; + + git_libgit2_init(); + + parse_opts(&o, argc, argv); + + /* Initialize repository. */ + + if (o.no_options) { + /** + * No options were specified, so let's demonstrate the default + * simple case of git_repository_init() API usage... + */ + check_lg2(git_repository_init(&repo, o.dir, 0), + "Could not initialize repository", NULL); + } + else { + /** + * Some command line options were specified, so we'll use the + * extended init API to handle them + */ + git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT; + initopts.flags = GIT_REPOSITORY_INIT_MKPATH; + + if (o.bare) + initopts.flags |= GIT_REPOSITORY_INIT_BARE; + + if (o.template) { + initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + initopts.template_path = o.template; + } + + if (o.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) + */ + initopts.workdir_path = o.dir; + o.dir = o.gitdir; + } + + if (o.shared != 0) + initopts.mode = o.shared; + + check_lg2(git_repository_init_ext(&repo, o.dir, &initopts), + "Could not initialize repository", NULL); + } + + /** Print a message to stdout like "git init" does. */ + + if (!o.quiet) { + if (o.bare || o.gitdir) + o.dir = git_repository_path(repo); + else + o.dir = git_repository_workdir(repo); + + printf("Initialized empty Git repository in %s\n", o.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 (o.initial_commit) { + create_initial_commit(repo); + printf("Created empty initial commit\n"); + } + + git_repository_free(repo); + git_libgit2_shutdown(); + + 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; + 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) + fatal("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) + fatal("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) + fatal("Unable to write initial tree from index", NULL); + + git_index_free(index); + + if (git_tree_lookup(&tree, repo, &tree_id) < 0) + fatal("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) + fatal("Could not create the initial commit", NULL); + + /** Clean up so we don't leak memory. */ + + git_tree_free(tree); + git_signature_free(sig); +} + +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=]\n" + " [--shared[=perms]] [--initial-commit]\n" + " [--separate-git-dir] \n"); + exit(1); +} + +/** Parse the tail of the --shared= argument. */ +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 parse_opts(struct opts *o, int argc, char *argv[]) +{ + struct args_info args = ARGS_INFO_INIT; + const char *sharedarg; + + /** Process arguments. */ + + for (args.pos = 1; args.pos < argc; ++args.pos) { + char *a = argv[args.pos]; + + if (a[0] == '-') + o->no_options = 0; + + if (a[0] != '-') { + if (o->dir != NULL) + usage("extra argument", a); + o->dir = a; + } + else if (!strcmp(a, "-q") || !strcmp(a, "--quiet")) + o->quiet = 1; + else if (!strcmp(a, "--bare")) + o->bare = 1; + else if (!strcmp(a, "--shared")) + o->shared = GIT_REPOSITORY_INIT_SHARED_GROUP; + else if (!strcmp(a, "--initial-commit")) + o->initial_commit = 1; + else if (match_str_arg(&sharedarg, &args, "--shared")) + o->shared = parse_shared(sharedarg); + else if (!match_str_arg(&o->template, &args, "--template") || + !match_str_arg(&o->gitdir, &args, "--separate-git-dir")) + usage("unknown option", a); + } + + if (!o->dir) + usage("must specify directory to init", NULL); +} diff --git a/tests/scripts/log.c b/tests/scripts/log.c new file mode 100644 index 0000000..e54eed3 --- /dev/null +++ b/tests/scripts/log.c @@ -0,0 +1,479 @@ +/* + * libgit2 "log" example - shows how to walk history and get commit info + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * . + */ + +#include "common.h" + +/** + * This example demonstrates the libgit2 rev walker APIs to roughly + * simulate the output of `git log` and a few of command line arguments. + * `git log` has many many options and this only shows a few of them. + * + * This does not have: + * + * - Robust error handling + * - Colorized or paginated output formatting + * - Most of the `git log` options + * + * This does have: + * + * - Examples of translating command line arguments to equivalent libgit2 + * revwalker configuration calls + * - Simplified options to apply pathspec limits and to show basic diffs + */ + +/** log_state represents walker being configured while handling options */ +struct log_state { + git_repository *repo; + const char *repodir; + git_revwalk *walker; + int hide; + int sorting; + int revisions; +}; + +/** utility functions that are called to configure the walker */ +static void set_sorting(struct log_state *s, unsigned int sort_mode); +static void push_rev(struct log_state *s, git_object *obj, int hide); +static int add_revision(struct log_state *s, const char *revstr); + +/** log_options holds other command line options that affect log output */ +struct log_options { + int show_diff; + int skip, limit; + int min_parents, max_parents; + git_time_t before; + git_time_t after; + const char *author; + const char *committer; + const char *grep; +}; + +/** utility functions that parse options and help with log output */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv); +static void print_time(const git_time *intime, const char *prefix); +static void print_commit(git_commit *commit); +static int match_with_parent(git_commit *commit, int i, git_diff_options *); + +/** utility functions for filtering */ +static int signature_matches(const git_signature *sig, const char *filter); +static int log_message_matches(const git_commit *commit, const char *filter); + +int main(int argc, char *argv[]) +{ + int i, count = 0, printed = 0, parents, last_arg; + struct log_state s; + struct log_options opt; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_oid oid; + git_commit *commit = NULL; + git_pathspec *ps = NULL; + + git_libgit2_init(); + + /** Parse arguments and set up revwalker. */ + + last_arg = parse_options(&s, &opt, argc, argv); + + diffopts.pathspec.strings = &argv[last_arg]; + diffopts.pathspec.count = argc - last_arg; + if (diffopts.pathspec.count > 0) + check_lg2(git_pathspec_new(&ps, &diffopts.pathspec), + "Building pathspec", NULL); + + if (!s.revisions) + add_revision(&s, NULL); + + /** Use the revwalker to traverse the history. */ + + printed = count = 0; + + for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) { + check_lg2(git_commit_lookup(&commit, s.repo, &oid), + "Failed to look up commit", NULL); + + parents = (int)git_commit_parentcount(commit); + if (parents < opt.min_parents) + continue; + if (opt.max_parents > 0 && parents > opt.max_parents) + continue; + + if (diffopts.pathspec.count > 0) { + int unmatched = parents; + + if (parents == 0) { + git_tree *tree; + check_lg2(git_commit_tree(&tree, commit), "Get tree", NULL); + if (git_pathspec_match_tree( + NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0) + unmatched = 1; + git_tree_free(tree); + } else if (parents == 1) { + unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1; + } else { + for (i = 0; i < parents; ++i) { + if (match_with_parent(commit, i, &diffopts)) + unmatched--; + } + } + + if (unmatched > 0) + continue; + } + + if (!signature_matches(git_commit_author(commit), opt.author)) + continue; + + if (!signature_matches(git_commit_committer(commit), opt.committer)) + continue; + + if (!log_message_matches(commit, opt.grep)) + continue; + + if (count++ < opt.skip) + continue; + if (opt.limit != -1 && printed++ >= opt.limit) { + git_commit_free(commit); + break; + } + + print_commit(commit); + + if (opt.show_diff) { + git_tree *a = NULL, *b = NULL; + git_diff *diff = NULL; + + if (parents > 1) + continue; + check_lg2(git_commit_tree(&b, commit), "Get tree", NULL); + if (parents == 1) { + git_commit *parent; + check_lg2(git_commit_parent(&parent, commit, 0), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + git_commit_free(parent); + } + + check_lg2(git_diff_tree_to_tree( + &diff, git_commit_owner(commit), a, b, &diffopts), + "Diff commit with parent", NULL); + check_lg2( + git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, diff_output, NULL), + "Displaying diff", NULL); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + } + } + + git_pathspec_free(ps); + git_revwalk_free(s.walker); + git_repository_free(s.repo); + git_libgit2_shutdown(); + + return 0; +} + +/** Determine if the given git_signature does not contain the filter text. */ +static int signature_matches(const git_signature *sig, const char *filter) { + if (filter == NULL) + return 1; + + if (sig != NULL && + (strstr(sig->name, filter) != NULL || + strstr(sig->email, filter) != NULL)) + return 1; + + return 0; +} + +static int log_message_matches(const git_commit *commit, const char *filter) { + const char *message = NULL; + + if (filter == NULL) + return 1; + + if ((message = git_commit_message(commit)) != NULL && + strstr(message, filter) != NULL) + return 1; + + return 0; +} + +/** Push object (for hide or show) onto revwalker. */ +static void push_rev(struct log_state *s, git_object *obj, int hide) +{ + hide = s->hide ^ hide; + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) { + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + git_revwalk_sorting(s->walker, s->sorting); + } + + if (!obj) + check_lg2(git_revwalk_push_head(s->walker), + "Could not find repository HEAD", NULL); + else if (hide) + check_lg2(git_revwalk_hide(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + else + check_lg2(git_revwalk_push(s->walker, git_object_id(obj)), + "Reference does not refer to a commit", NULL); + + git_object_free(obj); +} + +/** Parse revision string and add revs to walker. */ +static int add_revision(struct log_state *s, const char *revstr) +{ + git_revspec revs; + int hide = 0; + + /** Open repo on demand if it isn't already open. */ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + if (!revstr) { + push_rev(s, NULL, hide); + return 0; + } + + if (*revstr == '^') { + revs.flags = GIT_REVPARSE_SINGLE; + hide = !hide; + + if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0) + return -1; + } else if (git_revparse(&revs, s->repo, revstr) < 0) + return -1; + + if ((revs.flags & GIT_REVPARSE_SINGLE) != 0) + push_rev(s, revs.from, hide); + else { + push_rev(s, revs.to, hide); + + if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) { + git_oid base; + check_lg2(git_merge_base(&base, s->repo, + git_object_id(revs.from), git_object_id(revs.to)), + "Could not find merge base", revstr); + check_lg2( + git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT), + "Could not find merge base commit", NULL); + + push_rev(s, revs.to, hide); + } + + push_rev(s, revs.from, !hide); + } + + return 0; +} + +/** Update revwalker with sorting mode. */ +static void set_sorting(struct log_state *s, unsigned int sort_mode) +{ + /** Open repo on demand if it isn't already open. */ + if (!s->repo) { + if (!s->repodir) s->repodir = "."; + check_lg2(git_repository_open_ext(&s->repo, s->repodir, 0, NULL), + "Could not open repository", s->repodir); + } + + /** Create revwalker on demand if it doesn't already exist. */ + if (!s->walker) + check_lg2(git_revwalk_new(&s->walker, s->repo), + "Could not create revision walker", NULL); + + if (sort_mode == GIT_SORT_REVERSE) + s->sorting = s->sorting ^ GIT_SORT_REVERSE; + else + s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE); + + git_revwalk_sorting(s->walker, s->sorting); +} + +/** Helper to format a git_time value like Git. */ +static void print_time(const git_time *intime, const char *prefix) +{ + char sign, out[32]; + struct tm *intm; + int offset, hours, minutes; + time_t t; + + offset = intime->offset; + if (offset < 0) { + sign = '-'; + offset = -offset; + } else { + sign = '+'; + } + + hours = offset / 60; + minutes = offset % 60; + + t = (time_t)intime->time + (intime->offset * 60); + + intm = gmtime(&t); + strftime(out, sizeof(out), "%a %b %e %T %Y", intm); + + printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes); +} + +/** Helper to print a commit object. */ +static void print_commit(git_commit *commit) +{ + char buf[GIT_OID_HEXSZ + 1]; + int i, count; + const git_signature *sig; + const char *scan, *eol; + + git_oid_tostr(buf, sizeof(buf), git_commit_id(commit)); + printf("commit %s\n", buf); + + if ((count = (int)git_commit_parentcount(commit)) > 1) { + printf("Merge:"); + for (i = 0; i < count; ++i) { + git_oid_tostr(buf, 8, git_commit_parent_id(commit, i)); + printf(" %s", buf); + } + printf("\n"); + } + + if ((sig = git_commit_author(commit)) != NULL) { + printf("Author: %s <%s>\n", sig->name, sig->email); + print_time(&sig->when, "Date: "); + } + printf("\n"); + + for (scan = git_commit_message(commit); scan && *scan; ) { + for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */; + + printf(" %.*s\n", (int)(eol - scan), scan); + scan = *eol ? eol + 1 : NULL; + } + printf("\n"); +} + +/** Helper to find how many files in a commit changed from its nth parent. */ +static int match_with_parent(git_commit *commit, int i, git_diff_options *opts) +{ + git_commit *parent; + git_tree *a, *b; + git_diff *diff; + int ndeltas; + + check_lg2( + git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL); + check_lg2(git_commit_tree(&a, parent), "Tree for parent", NULL); + check_lg2(git_commit_tree(&b, commit), "Tree for commit", NULL); + check_lg2( + git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts), + "Checking diff between parent and commit", NULL); + + ndeltas = (int)git_diff_num_deltas(diff); + + git_diff_free(diff); + git_tree_free(a); + git_tree_free(b); + git_commit_free(parent); + + return ndeltas > 0; +} + +/** Print a usage message for the program. */ +static void usage(const char *message, const char *arg) +{ + if (message && arg) + fprintf(stderr, "%s: %s\n", message, arg); + else if (message) + fprintf(stderr, "%s\n", message); + fprintf(stderr, "usage: log []\n"); + exit(1); +} + +/** Parse some log command line options. */ +static int parse_options( + struct log_state *s, struct log_options *opt, int argc, char **argv) +{ + struct args_info args = ARGS_INFO_INIT; + + memset(s, 0, sizeof(*s)); + s->sorting = GIT_SORT_TIME; + + memset(opt, 0, sizeof(*opt)); + opt->max_parents = -1; + opt->limit = -1; + + for (args.pos = 1; args.pos < argc; ++args.pos) { + const char *a = argv[args.pos]; + + if (a[0] != '-') { + if (!add_revision(s, a)) + s->revisions++; + else + /** Try failed revision parse as filename. */ + break; + } else if (!strcmp(a, "--")) { + ++args.pos; + break; + } + else if (!strcmp(a, "--date-order")) + set_sorting(s, GIT_SORT_TIME); + else if (!strcmp(a, "--topo-order")) + set_sorting(s, GIT_SORT_TOPOLOGICAL); + else if (!strcmp(a, "--reverse")) + set_sorting(s, GIT_SORT_REVERSE); + else if (match_str_arg(&opt->author, &args, "--author")) + /** Found valid --author */; + else if (match_str_arg(&opt->committer, &args, "--committer")) + /** Found valid --committer */; + else if (match_str_arg(&opt->grep, &args, "--grep")) + /** Found valid --grep */; + else if (match_str_arg(&s->repodir, &args, "--git-dir")) + /** Found git-dir. */; + else if (match_int_arg(&opt->skip, &args, "--skip", 0)) + /** Found valid --skip. */; + else if (match_int_arg(&opt->limit, &args, "--max-count", 0)) + /** Found valid --max-count. */; + else if (a[1] >= '0' && a[1] <= '9') + is_integer(&opt->limit, a + 1, 0); + else if (match_int_arg(&opt->limit, &args, "-n", 0)) + /** Found valid -n. */; + else if (!strcmp(a, "--merges")) + opt->min_parents = 2; + else if (!strcmp(a, "--no-merges")) + opt->max_parents = 1; + else if (!strcmp(a, "--no-min-parents")) + opt->min_parents = 0; + else if (!strcmp(a, "--no-max-parents")) + opt->max_parents = -1; + else if (match_int_arg(&opt->max_parents, &args, "--max-parents=", 1)) + /** Found valid --max-parents. */; + else if (match_int_arg(&opt->min_parents, &args, "--min-parents=", 0)) + /** Found valid --min_parents. */; + else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch")) + opt->show_diff = 1; + else + usage("Unsupported argument", a); + } + + return args.pos; +} + diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh new file mode 100755 index 0000000..3a1f71c --- /dev/null +++ b/tests/scripts/run_tests.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -eux + +make + +if [ ! -e ~/.gitconfig ]; then + cat << EOF > ~/.gitconfig +[user] +name=test-user +email=test-email +EOF +fi + +./init --initial-commit ./test-libgit2 +pushd ./test-libgit2 +../log | grep 'Initial commit' diff --git a/tests/tests.yml b/tests/tests.yml new file mode 100644 index 0000000..84fc80c --- /dev/null +++ b/tests/tests.yml @@ -0,0 +1,18 @@ +--- +# Run a simple test +- hosts: localhost + roles: + - role: standard-test-basic + tags: + - classic + + required_packages: + - make + - gcc + - libgit2 + - libgit2-devel + + tests: + - simple: + dir: scripts + run: ./run_tests.sh