Commit 66902d47 by Russell Belfer

Extract common example helpers and reorg examples

This reorganizes a few of the examples so that the main function
comes first with the argument parsing extracted into a helper
that can come at the end of the file (so the example focuses more
on the use of libgit2 instead of command line support).  This also
creates a shared examples/common.[ch] so that useful helper funcs
can be shared across examples instead of repeated.
parent 5c50f22a
......@@ -9,6 +9,8 @@ ENDIF()
FILE(GLOB SRC_EXAMPLE_APPS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c)
FOREACH(src_app ${SRC_EXAMPLE_APPS})
STRING(REPLACE ".c" "" app_name ${src_app})
ADD_EXECUTABLE(${app_name} ${src_app})
IF(NOT ${app_name} STREQUAL "common")
ADD_EXECUTABLE(${app_name} ${src_app} "common.c")
TARGET_LINK_LIBRARIES(${app_name} git2)
ENDIF()
ENDFOREACH()
......@@ -8,7 +8,7 @@ APPS = general showindex diff rev-list cat-file status log rev-parse init
all: $(APPS)
% : %.c
$(CC) -o $@ $(CFLAGS) $< $(LFLAGS)
$(CC) -o $@ common.c $(CFLAGS) $< $(LFLAGS)
clean:
$(RM) $(APPS)
......
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#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 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;
}
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 = 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);
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <git2.h>
/**
* 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; 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 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);
......@@ -4,18 +4,8 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include <git2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {
FORMAT_DEFAULT = 0,
FORMAT_LONG = 1,
FORMAT_SHORT = 2,
FORMAT_PORCELAIN = 3,
};
#define MAX_PATHSPEC 8
#include "common.h"
/*
* This example demonstrates the use of the libgit2 status APIs,
......@@ -35,32 +25,83 @@ enum {
* - A sample status formatter that matches the "short" format
*/
static void check(int error, const char *message, const char *extra)
enum {
FORMAT_DEFAULT = 0,
FORMAT_LONG = 1,
FORMAT_SHORT = 2,
FORMAT_PORCELAIN = 3,
};
#define MAX_PATHSPEC 8
struct opts {
git_status_options statusopt;
char *repodir;
char *pathspec[MAX_PATHSPEC];
int npaths;
int format;
int zterm;
int showbranch;
};
static void parse_opts(struct opts *o, int argc, char *argv[]);
static void show_branch(git_repository *repo, int format);
static void print_long(git_repository *repo, git_status_list *status);
static void print_short(git_repository *repo, git_status_list *status);
int main(int argc, char *argv[])
{
const git_error *lg2err;
const char *lg2msg = "", *lg2spacer = "";
git_repository *repo = NULL;
git_status_list *status;
struct opts o = { GIT_STATUS_OPTIONS_INIT, "." };
if (!error)
return;
git_threads_init();
if ((lg2err = giterr_last()) != NULL && lg2err->message != NULL) {
lg2msg = lg2err->message;
lg2spacer = " - ";
}
o.statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
o.statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
parse_opts(&o, argc, argv);
/*
* Try to open the repository at the given path (or at the current
* directory if none was given).
*/
check_lg2(git_repository_open_ext(&repo, o.repodir, 0, NULL),
"Could not open repository", o.repodir);
if (git_repository_is_bare(repo))
fatal("Cannot report status on bare repository",
git_repository_path(repo));
/*
* Run status on the repository
*
* Because we want to simluate a full "git status" run and want to
* support some command line options, we use `git_status_foreach_ext()`
* instead of just the plain status call. This allows (a) iterating
* over the index and then the workdir and (b) extra flags that control
* which files are included. If you just want simple status (e.g. to
* enumerate files that are modified) then you probably don't need the
* extended API.
*/
check_lg2(git_status_list_new(&status, repo, &o.statusopt),
"Could not get status", NULL);
if (extra)
fprintf(stderr, "%s '%s' [%d]%s%s\n",
message, extra, error, lg2spacer, lg2msg);
if (o.showbranch)
show_branch(repo, o.format);
if (o.format == FORMAT_LONG)
print_long(repo, status);
else
fprintf(stderr, "%s [%d]%s%s\n",
message, error, lg2spacer, lg2msg);
print_short(repo, status);
exit(1);
}
git_status_list_free(status);
git_repository_free(repo);
git_threads_shutdown();
static void fail(const char *message)
{
check(-1, message, NULL);
return 0;
}
static void show_branch(git_repository *repo, int format)
......@@ -78,7 +119,7 @@ static void show_branch(git_repository *repo, int format)
if (!strncmp(branch, "refs/heads/", strlen("refs/heads/")))
branch += strlen("refs/heads/");
} else
check(error, "failed to get current branch", NULL);
check_lg2(error, "failed to get current branch", NULL);
if (format == FORMAT_LONG)
printf("# On branch %s\n",
......@@ -341,103 +382,58 @@ static void print_short(git_repository *repo, git_status_list *status)
}
}
int main(int argc, char *argv[])
static void parse_opts(struct opts *o, int argc, char *argv[])
{
git_repository *repo = NULL;
int i, npaths = 0, format = FORMAT_DEFAULT, zterm = 0, showbranch = 0;
git_status_options opt = GIT_STATUS_OPTIONS_INIT;
git_status_list *status;
char *repodir = ".", *pathspec[MAX_PATHSPEC];
struct args_info args = ARGS_INFO_INIT;
opt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
opt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
for (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
if (npaths < MAX_PATHSPEC)
pathspec[npaths++] = argv[i];
if (a[0] != '-') {
if (o->npaths < MAX_PATHSPEC)
o->pathspec[o->npaths++] = a;
else
fail("Example only supports a limited pathspec");
fatal("Example only supports a limited pathspec", NULL);
}
else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--short"))
format = FORMAT_SHORT;
else if (!strcmp(argv[i], "--long"))
format = FORMAT_LONG;
else if (!strcmp(argv[i], "--porcelain"))
format = FORMAT_PORCELAIN;
else if (!strcmp(argv[i], "-b") || !strcmp(argv[i], "--branch"))
showbranch = 1;
else if (!strcmp(argv[i], "-z")) {
zterm = 1;
if (format == FORMAT_DEFAULT)
format = FORMAT_PORCELAIN;
else if (!strcmp(a, "-s") || !strcmp(a, "--short"))
o->format = FORMAT_SHORT;
else if (!strcmp(a, "--long"))
o->format = FORMAT_LONG;
else if (!strcmp(a, "--porcelain"))
o->format = FORMAT_PORCELAIN;
else if (!strcmp(a, "-b") || !strcmp(a, "--branch"))
o->showbranch = 1;
else if (!strcmp(a, "-z")) {
o->zterm = 1;
if (o->format == FORMAT_DEFAULT)
o->format = FORMAT_PORCELAIN;
}
else if (!strcmp(argv[i], "--ignored"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
else if (!strcmp(argv[i], "-uno") ||
!strcmp(argv[i], "--untracked-files=no"))
opt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(argv[i], "-unormal") ||
!strcmp(argv[i], "--untracked-files=normal"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(argv[i], "-uall") ||
!strcmp(argv[i], "--untracked-files=all"))
opt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
else if (!strcmp(a, "--ignored"))
o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
else if (!strcmp(a, "-uno") ||
!strcmp(a, "--untracked-files=no"))
o->statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(a, "-unormal") ||
!strcmp(a, "--untracked-files=normal"))
o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (!strcmp(a, "-uall") ||
!strcmp(a, "--untracked-files=all"))
o->statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
else if (!strcmp(argv[i], "--ignore-submodules=all"))
opt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (!strncmp(argv[i], "--git-dir=", strlen("--git-dir=")))
repodir = argv[i] + strlen("--git-dir=");
else if (!strcmp(a, "--ignore-submodules=all"))
o->statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
o->repodir = a + strlen("--git-dir=");
else
check(-1, "Unsupported option", argv[i]);
check_lg2(-1, "Unsupported option", a);
}
if (format == FORMAT_DEFAULT)
format = FORMAT_LONG;
if (format == FORMAT_LONG)
showbranch = 1;
if (npaths > 0) {
opt.pathspec.strings = pathspec;
opt.pathspec.count = npaths;
if (o->format == FORMAT_DEFAULT)
o->format = FORMAT_LONG;
if (o->format == FORMAT_LONG)
o->showbranch = 1;
if (o->npaths > 0) {
o->statusopt.pathspec.strings = o->pathspec;
o->statusopt.pathspec.count = o->npaths;
}
/*
* Try to open the repository at the given path (or at the current
* directory if none was given).
*/
check(git_repository_open_ext(&repo, repodir, 0, NULL),
"Could not open repository", repodir);
if (git_repository_is_bare(repo))
fail("Cannot report status on bare repository");
/*
* Run status on the repository
*
* Because we want to simluate a full "git status" run and want to
* support some command line options, we use `git_status_foreach_ext()`
* instead of just the plain status call. This allows (a) iterating
* over the index and then the workdir and (b) extra flags that control
* which files are included. If you just want simple status (e.g. to
* enumerate files that are modified) then you probably don't need the
* extended API.
*/
check(git_status_list_new(&status, repo, &opt),
"Could not get status", NULL);
if (showbranch)
show_branch(repo, format);
if (format == FORMAT_LONG)
print_long(repo, status);
else
print_short(repo, status);
git_status_list_free(status);
git_repository_free(repo);
return 0;
}
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