Initial commit - full huishou project

This commit is contained in:
jiapengyu
2026-07-27 14:07:26 +08:00
commit 60790ad1b3
39127 changed files with 5989265 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
INCLUDE_DIRECTORIES(${LIBGIT2_INCLUDES})
INCLUDE_DIRECTORIES(SYSTEM ${LIBGIT2_SYSTEM_INCLUDES})
FILE(GLOB LG2_SOURCES *.c *.h)
ADD_EXECUTABLE(lg2 ${LG2_SOURCES})
SET_TARGET_PROPERTIES(lg2 PROPERTIES C_STANDARD 90)
# Ensure that we do not use deprecated functions internally
ADD_DEFINITIONS(-DGIT_DEPRECATE_HARD)
IF(WIN32 OR ANDROID)
TARGET_LINK_LIBRARIES(lg2 git2)
ELSE()
TARGET_LINK_LIBRARIES(lg2 git2 pthread)
ENDIF()
+121
View File
@@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.
+22
View File
@@ -0,0 +1,22 @@
libgit2 examples
================
These examples are a mixture of basic emulation of core Git command line
functions and simple snippets demonstrating libgit2 API usage (for use
with Docurium). As a whole, they are not vetted carefully for bugs, error
handling, and cross-platform compatibility in the same manner as the rest
of the code in libgit2, so copy with caution.
That being said, you are welcome to copy code from these examples as
desired when using libgit2. They have been [released to the public domain][cc0],
so there are no restrictions on their use.
[cc0]: COPYING
For annotated HTML versions, see the "Examples" section of:
http://libgit2.github.com/libgit2
such as:
http://libgit2.github.com/libgit2/ex/HEAD/general.html
+170
View File
@@ -0,0 +1,170 @@
/*
* libgit2 "add" example - shows how to modify the index
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* The following example demonstrates how to add files with libgit2.
*
* It will use the repository in the current working directory, and act
* on files passed as its parameters.
*
* Recognized options are:
* -v/--verbose: show the file's status after acting on it.
* -n/--dry-run: do not actually change the index.
* -u/--update: update the index instead of adding to it.
*/
enum index_mode {
INDEX_NONE,
INDEX_ADD,
};
struct index_options {
int dry_run;
int verbose;
git_repository *repo;
enum index_mode mode;
int add_update;
};
/* Forward declarations for helpers */
static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args);
int print_matched_cb(const char *path, const char *matched_pathspec, void *payload);
int lg2_add(git_repository *repo, int argc, char **argv)
{
git_index_matched_path_cb matched_cb = NULL;
git_index *index;
git_strarray array = {0};
struct index_options options;
struct args_info args = ARGS_INFO_INIT;
/* Parse the options & arguments. */
parse_opts(NULL, &options, &args);
strarray_from_args(&array, &args);
/* Grab the repository's index. */
check_lg2(git_repository_index(&index, repo), "Could not open repository index", NULL);
/* Setup a callback if the requested options need it */
if (options.verbose || options.dry_run) {
matched_cb = &print_matched_cb;
}
options.repo = repo;
/* Perform the requested action with the index and files */
if (options.add_update) {
git_index_update_all(index, &array, matched_cb, &options);
} else {
git_index_add_all(index, &array, 0, matched_cb, &options);
}
/* Cleanup memory */
git_index_write(index);
git_index_free(index);
return 0;
}
/*
* This callback is called for each file under consideration by
* git_index_(update|add)_all above.
* It makes uses of the callback's ability to abort the action.
*/
int print_matched_cb(const char *path, const char *matched_pathspec, void *payload)
{
struct index_options *opts = (struct index_options *)(payload);
int ret;
unsigned status;
(void)matched_pathspec;
/* Get the file status */
if (git_status_file(&status, opts->repo, path) < 0)
return -1;
if ((status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW)) {
printf("add '%s'\n", path);
ret = 0;
} else {
ret = 1;
}
if (opts->dry_run)
ret = 1;
return ret;
}
void init_array(git_strarray *array, int argc, char **argv)
{
unsigned int i;
array->count = argc;
array->strings = calloc(array->count, sizeof(char *));
assert(array->strings != NULL);
for (i = 0; i < array->count; i++) {
array->strings[i] = argv[i];
}
return;
}
void print_usage(void)
{
fprintf(stderr, "usage: add [options] [--] file-spec [file-spec] [...]\n\n");
fprintf(stderr, "\t-n, --dry-run dry run\n");
fprintf(stderr, "\t-v, --verbose be verbose\n");
fprintf(stderr, "\t-u, --update update tracked files\n");
exit(1);
}
static void parse_opts(const char **repo_path, struct index_options *opts, struct args_info *args)
{
if (args->argc <= 1)
print_usage();
for (args->pos = 1; args->pos < args->argc; ++args->pos) {
const char *curr = args->argv[args->pos];
if (curr[0] != '-') {
if (!strcmp("add", curr)) {
opts->mode = INDEX_ADD;
continue;
} else if (opts->mode == INDEX_NONE) {
fprintf(stderr, "missing command: %s", curr);
print_usage();
break;
} else {
/* We might be looking at a filename */
break;
}
} else if (match_bool_arg(&opts->verbose, args, "--verbose") ||
match_bool_arg(&opts->dry_run, args, "--dry-run") ||
match_str_arg(repo_path, args, "--git-dir") ||
(opts->mode == INDEX_ADD && match_bool_arg(&opts->add_update, args, "--update"))) {
continue;
} else if (match_bool_arg(NULL, args, "--help")) {
print_usage();
break;
} else if (match_arg_separator(args)) {
break;
} else {
fprintf(stderr, "Unsupported option %s.\n", curr);
print_usage();
}
}
}
+197
View File
@@ -0,0 +1,197 @@
#include "common.h"
#include "args.h"
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 match_bool_arg(int *out, struct args_info *args, const char *opt)
{
const char *found = args->argv[args->pos];
if (!strcmp(found, opt)) {
*out = 1;
return 1;
}
if (!strncmp(found, "--no-", strlen("--no-")) &&
!strcmp(found + strlen("--no-"), opt + 2)) {
*out = 0;
return 1;
}
*out = -1;
return 0;
}
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 match_arg_separator(struct args_info *args)
{
if (args->opts_done)
return 1;
if (strcmp(args->argv[args->pos], "--") != 0)
return 0;
args->opts_done = 1;
args->pos++;
return 1;
}
void strarray_from_args(git_strarray *array, struct args_info *args)
{
size_t i;
array->count = args->argc - args->pos;
array->strings = calloc(array->count, sizeof(char *));
assert(array->strings != NULL);
for (i = 0; args->pos < args->argc; ++args->pos) {
array->strings[i++] = args->argv[args->pos];
}
args->pos = args->argc;
}
+90
View File
@@ -0,0 +1,90 @@
#ifndef INCLUDE_examples_args_h__
#define INCLUDE_examples_args_h__
/**
* Argument-processing helper structure
*/
struct args_info {
int argc;
char **argv;
int pos;
int opts_done : 1; /**< Did we see a -- separator */
};
#define ARGS_INFO_INIT { argc, argv, 0, 0 }
#define ARGS_CURRENT(args) args->argv[args->pos]
/**
* 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);
/**
* 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);
/**
* Check current `args` entry against a "bool" `opt` (ie. --[no-]progress).
* If `opt` matches positively, out will be set to 1, or if `opt` matches
* negatively, out will be set to 0, and in both cases 1 will be returned.
* If neither the positive or the negative form of opt matched, out will be -1,
* and 0 will be returned.
*/
extern int match_bool_arg(int *out, struct args_info *args, const char *opt);
/**
* Check if we're processing past the single -- separator
*/
extern int match_arg_separator(struct args_info *args);
/**
* Consume all remaining arguments in a git_strarray
*/
extern void strarray_from_args(git_strarray *array, struct args_info *args);
#endif
+198
View File
@@ -0,0 +1,198 @@
/*
* libgit2 "blame" example - shows how to use the blame API
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates how to invoke the libgit2 blame API to roughly
* simulate the output of `git blame` and a few of its command line arguments.
*/
struct blame_opts {
char *path;
char *commitspec;
int C;
int M;
int start_line;
int end_line;
int F;
};
static void parse_opts(struct blame_opts *o, int argc, char *argv[]);
int lg2_blame(git_repository *repo, int argc, char *argv[])
{
int line, break_on_null_hunk;
git_object_size_t i, rawsize;
char spec[1024] = {0};
struct blame_opts o = {0};
const char *rawdata;
git_revspec revspec = {0};
git_blame_options blameopts = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
git_blob *blob;
git_object *obj;
parse_opts(&o, argc, argv);
if (o.M) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
if (o.C) blameopts.flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
if (o.F) blameopts.flags |= GIT_BLAME_FIRST_PARENT;
/**
* The commit range comes in "commitish" form. Use the rev-parse API to
* nail down the end points.
*/
if (o.commitspec) {
check_lg2(git_revparse(&revspec, repo, o.commitspec), "Couldn't parse commit spec", NULL);
if (revspec.flags & GIT_REVPARSE_SINGLE) {
git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.from));
git_object_free(revspec.from);
} else {
git_oid_cpy(&blameopts.oldest_commit, git_object_id(revspec.from));
git_oid_cpy(&blameopts.newest_commit, git_object_id(revspec.to));
git_object_free(revspec.from);
git_object_free(revspec.to);
}
}
/** Run the blame. */
check_lg2(git_blame_file(&blame, repo, o.path, &blameopts), "Blame error", NULL);
/**
* Get the raw data inside the blob for output. We use the
* `commitish:path/to/file.txt` format to find it.
*/
if (git_oid_is_zero(&blameopts.newest_commit))
strcpy(spec, "HEAD");
else
git_oid_tostr(spec, sizeof(spec), &blameopts.newest_commit);
strcat(spec, ":");
strcat(spec, o.path);
check_lg2(git_revparse_single(&obj, repo, spec), "Object lookup error", NULL);
check_lg2(git_blob_lookup(&blob, repo, git_object_id(obj)), "Blob lookup error", NULL);
git_object_free(obj);
rawdata = git_blob_rawcontent(blob);
rawsize = git_blob_rawsize(blob);
/** Produce the output. */
line = 1;
i = 0;
break_on_null_hunk = 0;
while (i < rawsize) {
const char *eol = memchr(rawdata + i, '\n', (size_t)(rawsize - i));
char oid[10] = {0};
const git_blame_hunk *hunk = git_blame_get_hunk_byline(blame, line);
if (break_on_null_hunk && !hunk)
break;
if (hunk) {
char sig[128] = {0};
break_on_null_hunk = 1;
git_oid_tostr(oid, 10, &hunk->final_commit_id);
snprintf(sig, 30, "%s <%s>", hunk->final_signature->name, hunk->final_signature->email);
printf("%s ( %-30s %3d) %.*s\n",
oid,
sig,
line,
(int)(eol - rawdata - i),
rawdata + i);
}
i = (int)(eol - rawdata + 1);
line++;
}
/** Cleanup. */
git_blob_free(blob);
git_blame_free(blame);
return 0;
}
/** Tell the user how to make this thing work. */
static void usage(const char *msg, const char *arg)
{
if (msg && arg)
fprintf(stderr, "%s: %s\n", msg, arg);
else if (msg)
fprintf(stderr, "%s\n", msg);
fprintf(stderr, "usage: blame [options] [<commit range>] <path>\n");
fprintf(stderr, "\n");
fprintf(stderr, " <commit range> example: `HEAD~10..HEAD`, or `1234abcd`\n");
fprintf(stderr, " -L <n,m> process only line range n-m, counting from 1\n");
fprintf(stderr, " -M find line moves within and across files\n");
fprintf(stderr, " -C find line copies within and across files\n");
fprintf(stderr, " -F follow only the first parent commits\n");
fprintf(stderr, "\n");
exit(1);
}
/** Parse the arguments. */
static void parse_opts(struct blame_opts *o, int argc, char *argv[])
{
int i;
char *bare_args[3] = {0};
if (argc < 2) usage(NULL, NULL);
for (i=1; i<argc; i++) {
char *a = argv[i];
if (a[0] != '-') {
int i=0;
while (bare_args[i] && i < 3) ++i;
if (i >= 3)
usage("Invalid argument set", NULL);
bare_args[i] = a;
}
else if (!strcmp(a, "--"))
continue;
else if (!strcasecmp(a, "-M"))
o->M = 1;
else if (!strcasecmp(a, "-C"))
o->C = 1;
else if (!strcasecmp(a, "-F"))
o->F = 1;
else if (!strcasecmp(a, "-L")) {
i++; a = argv[i];
if (i >= argc) fatal("Not enough arguments to -L", NULL);
check_lg2(sscanf(a, "%d,%d", &o->start_line, &o->end_line)-2, "-L format error", NULL);
}
else {
/* commit range */
if (o->commitspec) fatal("Only one commit spec allowed", NULL);
o->commitspec = a;
}
}
/* Handle the bare arguments */
if (!bare_args[0]) usage("Please specify a path", NULL);
o->path = bare_args[0];
if (bare_args[1]) {
/* <commitspec> <path> */
o->path = bare_args[1];
o->commitspec = bare_args[0];
}
if (bare_args[2]) {
/* <oldcommit> <newcommit> <path> */
char spec[128] = {0};
o->path = bare_args[2];
sprintf(spec, "%s..%s", bare_args[0], bare_args[1]);
o->commitspec = spec;
}
}
+239
View File
@@ -0,0 +1,239 @@
/*
* libgit2 "cat-file" example - shows how to print data from the ODB
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
static void print_signature(const char *header, const git_signature *sig)
{
char sign;
int offset, hours, minutes;
if (!sig)
return;
offset = sig->when.offset;
if (offset < 0) {
sign = '-';
offset = -offset;
} else {
sign = '+';
}
hours = offset / 60;
minutes = offset % 60;
printf("%s %s <%s> %ld %c%02d%02d\n",
header, sig->name, sig->email, (long)sig->when.time,
sign, hours, minutes);
}
/** Printing out a blob is simple, get the contents and print */
static void show_blob(const git_blob *blob)
{
/* ? Does this need crlf filtering? */
fwrite(git_blob_rawcontent(blob), (size_t)git_blob_rawsize(blob), 1, stdout);
}
/** Show each entry with its type, id and attributes */
static void show_tree(const git_tree *tree)
{
size_t i, max_i = (int)git_tree_entrycount(tree);
char oidstr[GIT_OID_HEXSZ + 1];
const git_tree_entry *te;
for (i = 0; i < max_i; ++i) {
te = git_tree_entry_byindex(tree, i);
git_oid_tostr(oidstr, sizeof(oidstr), git_tree_entry_id(te));
printf("%06o %s %s\t%s\n",
git_tree_entry_filemode(te),
git_object_type2string(git_tree_entry_type(te)),
oidstr, git_tree_entry_name(te));
}
}
/**
* Commits and tags have a few interesting fields in their header.
*/
static void show_commit(const git_commit *commit)
{
unsigned int i, max_i;
char oidstr[GIT_OID_HEXSZ + 1];
git_oid_tostr(oidstr, sizeof(oidstr), git_commit_tree_id(commit));
printf("tree %s\n", oidstr);
max_i = (unsigned int)git_commit_parentcount(commit);
for (i = 0; i < max_i; ++i) {
git_oid_tostr(oidstr, sizeof(oidstr), git_commit_parent_id(commit, i));
printf("parent %s\n", oidstr);
}
print_signature("author", git_commit_author(commit));
print_signature("committer", git_commit_committer(commit));
if (git_commit_message(commit))
printf("\n%s\n", git_commit_message(commit));
}
static void show_tag(const git_tag *tag)
{
char oidstr[GIT_OID_HEXSZ + 1];
git_oid_tostr(oidstr, sizeof(oidstr), git_tag_target_id(tag));;
printf("object %s\n", oidstr);
printf("type %s\n", git_object_type2string(git_tag_target_type(tag)));
printf("tag %s\n", git_tag_name(tag));
print_signature("tagger", git_tag_tagger(tag));
if (git_tag_message(tag))
printf("\n%s\n", git_tag_message(tag));
}
typedef enum {
SHOW_TYPE = 1,
SHOW_SIZE = 2,
SHOW_NONE = 3,
SHOW_PRETTY = 4
} catfile_mode;
/* Forward declarations for option-parsing helper */
struct catfile_options {
const char *dir;
const char *rev;
catfile_mode action;
int verbose;
};
static void parse_opts(struct catfile_options *o, int argc, char *argv[]);
/** Entry point for this command */
int lg2_cat_file(git_repository *repo, int argc, char *argv[])
{
struct catfile_options o = { ".", NULL, 0, 0 };
git_object *obj = NULL;
char oidstr[GIT_OID_HEXSZ + 1];
parse_opts(&o, argc, argv);
check_lg2(git_revparse_single(&obj, repo, o.rev),
"Could not resolve", o.rev);
if (o.verbose) {
char oidstr[GIT_OID_HEXSZ + 1];
git_oid_tostr(oidstr, sizeof(oidstr), git_object_id(obj));
printf("%s %s\n--\n",
git_object_type2string(git_object_type(obj)), oidstr);
}
switch (o.action) {
case SHOW_TYPE:
printf("%s\n", git_object_type2string(git_object_type(obj)));
break;
case SHOW_SIZE: {
git_odb *odb;
git_odb_object *odbobj;
check_lg2(git_repository_odb(&odb, repo), "Could not open ODB", NULL);
check_lg2(git_odb_read(&odbobj, odb, git_object_id(obj)),
"Could not find obj", NULL);
printf("%ld\n", (long)git_odb_object_size(odbobj));
git_odb_object_free(odbobj);
git_odb_free(odb);
}
break;
case SHOW_NONE:
/* just want return result */
break;
case SHOW_PRETTY:
switch (git_object_type(obj)) {
case GIT_OBJECT_BLOB:
show_blob((const git_blob *)obj);
break;
case GIT_OBJECT_COMMIT:
show_commit((const git_commit *)obj);
break;
case GIT_OBJECT_TREE:
show_tree((const git_tree *)obj);
break;
case GIT_OBJECT_TAG:
show_tag((const git_tag *)obj);
break;
default:
printf("unknown %s\n", oidstr);
break;
}
break;
}
git_object_free(obj);
return 0;
}
/** Print out usage information */
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: cat-file (-t | -s | -e | -p) [-v] [-q] "
"[-h|--help] [--git-dir=<dir>] <object>\n");
exit(1);
}
/** Parse the command-line options taken from git */
static void parse_opts(struct catfile_options *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
if (a[0] != '-') {
if (o->rev != NULL)
usage("Only one rev should be provided", NULL);
else
o->rev = a;
}
else if (!strcmp(a, "-t"))
o->action = SHOW_TYPE;
else if (!strcmp(a, "-s"))
o->action = SHOW_SIZE;
else if (!strcmp(a, "-e"))
o->action = SHOW_NONE;
else if (!strcmp(a, "-p"))
o->action = SHOW_PRETTY;
else if (!strcmp(a, "-q"))
o->verbose = 0;
else if (!strcmp(a, "-v"))
o->verbose = 1;
else if (!strcmp(a, "--help") || !strcmp(a, "-h"))
usage(NULL, NULL);
else if (!match_str_arg(&o->dir, &args, "--git-dir"))
usage("Unknown option", a);
}
if (!o->action || !o->rev)
usage(NULL, NULL);
}
+290
View File
@@ -0,0 +1,290 @@
/*
* libgit2 "checkout" example - shows how to perform checkouts
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)
# define PRIuZ "Iu"
# define PRIxZ "Ix"
# define PRIdZ "Id"
#else
# define PRIuZ "zu"
# define PRIxZ "zx"
# define PRIdZ "zd"
#endif
/**
* The following example demonstrates how to do checkouts with libgit2.
*
* Recognized options are :
* --force: force the checkout to happen.
* --[no-]progress: show checkout progress, on by default.
* --perf: show performance data.
*/
typedef struct {
int force : 1;
int progress : 1;
int perf : 1;
} checkout_options;
static void print_usage(void)
{
fprintf(stderr, "usage: checkout [options] <branch>\n"
"Options are :\n"
" --git-dir: use the following git repository.\n"
" --force: force the checkout.\n"
" --[no-]progress: show checkout progress.\n"
" --perf: show performance data.\n");
exit(1);
}
static void parse_options(const char **repo_path, checkout_options *opts, struct args_info *args)
{
if (args->argc <= 1)
print_usage();
memset(opts, 0, sizeof(*opts));
/* Default values */
opts->progress = 1;
for (args->pos = 1; args->pos < args->argc; ++args->pos) {
const char *curr = args->argv[args->pos];
int bool_arg;
if (match_arg_separator(args)) {
break;
} else if (!strcmp(curr, "--force")) {
opts->force = 1;
} else if (match_bool_arg(&bool_arg, args, "--progress")) {
opts->progress = bool_arg;
} else if (match_bool_arg(&bool_arg, args, "--perf")) {
opts->perf = bool_arg;
} else if (match_str_arg(repo_path, args, "--git-dir")) {
continue;
} else {
break;
}
}
}
/**
* This function is called to report progression, ie. it's called once with
* a NULL path and the number of total steps, then for each subsequent path,
* the current completed_step value.
*/
static void print_checkout_progress(const char *path, size_t completed_steps, size_t total_steps, void *payload)
{
(void)payload;
if (path == NULL) {
printf("checkout started: %" PRIuZ " steps\n", total_steps);
} else {
printf("checkout: %s %" PRIuZ "/%" PRIuZ "\n", path, completed_steps, total_steps);
}
}
/**
* This function is called when the checkout completes, and is used to report the
* number of syscalls performed.
*/
static void print_perf_data(const git_checkout_perfdata *perfdata, void *payload)
{
(void)payload;
printf("perf: stat: %" PRIuZ " mkdir: %" PRIuZ " chmod: %" PRIuZ "\n",
perfdata->stat_calls, perfdata->mkdir_calls, perfdata->chmod_calls);
}
/**
* This is the main "checkout <branch>" function, responsible for performing
* a branch-based checkout.
*/
static int perform_checkout_ref(git_repository *repo, git_annotated_commit *target, const char *target_ref, checkout_options *opts)
{
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
git_reference *ref = NULL, *branch = NULL;
git_commit *target_commit = NULL;
int err;
/** Setup our checkout options from the parsed options */
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
if (opts->force)
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
if (opts->progress)
checkout_opts.progress_cb = print_checkout_progress;
if (opts->perf)
checkout_opts.perfdata_cb = print_perf_data;
/** Grab the commit we're interested to move to */
err = git_commit_lookup(&target_commit, repo, git_annotated_commit_id(target));
if (err != 0) {
fprintf(stderr, "failed to lookup commit: %s\n", git_error_last()->message);
goto cleanup;
}
/**
* Perform the checkout so the workdir corresponds to what target_commit
* contains.
*
* Note that it's okay to pass a git_commit here, because it will be
* peeled to a tree.
*/
err = git_checkout_tree(repo, (const git_object *)target_commit, &checkout_opts);
if (err != 0) {
fprintf(stderr, "failed to checkout tree: %s\n", git_error_last()->message);
goto cleanup;
}
/**
* Now that the checkout has completed, we have to update HEAD.
*
* Depending on the "origin" of target (ie. it's an OID or a branch name),
* we might need to detach HEAD.
*/
if (git_annotated_commit_ref(target)) {
const char *target_head;
if ((err = git_reference_lookup(&ref, repo, git_annotated_commit_ref(target))) < 0)
goto error;
if (git_reference_is_remote(ref)) {
if ((err = git_branch_create_from_annotated(&branch, repo, target_ref, target, 0)) < 0)
goto error;
target_head = git_reference_name(branch);
} else {
target_head = git_annotated_commit_ref(target);
}
err = git_repository_set_head(repo, target_head);
} else {
err = git_repository_set_head_detached_from_annotated(repo, target);
}
error:
if (err != 0) {
fprintf(stderr, "failed to update HEAD reference: %s\n", git_error_last()->message);
goto cleanup;
}
cleanup:
git_commit_free(target_commit);
git_reference_free(branch);
git_reference_free(ref);
return err;
}
/**
* This corresponds to `git switch --guess`: if a given ref does
* not exist, git will by default try to guess the reference by
* seeing whether any remote has a branch called <ref>. If there
* is a single remote only that has it, then it is assumed to be
* the desired reference and a local branch is created for it.
*
* The following is a simplified implementation. It will not try
* to check whether the ref is unique across all remotes.
*/
static int guess_refish(git_annotated_commit **out, git_repository *repo, const char *ref)
{
git_strarray remotes = { NULL, 0 };
git_reference *remote_ref = NULL;
int error;
size_t i;
if ((error = git_remote_list(&remotes, repo)) < 0)
goto out;
for (i = 0; i < remotes.count; i++) {
char *refname = NULL;
size_t reflen;
reflen = snprintf(refname, 0, "refs/remotes/%s/%s", remotes.strings[i], ref);
if ((refname = malloc(reflen + 1)) == NULL) {
error = -1;
goto next;
}
snprintf(refname, reflen + 1, "refs/remotes/%s/%s", remotes.strings[i], ref);
if ((error = git_reference_lookup(&remote_ref, repo, refname)) < 0)
goto next;
break;
next:
free(refname);
if (error < 0 && error != GIT_ENOTFOUND)
break;
}
if (!remote_ref) {
error = GIT_ENOTFOUND;
goto out;
}
if ((error = git_annotated_commit_from_ref(out, repo, remote_ref)) < 0)
goto out;
out:
git_reference_free(remote_ref);
git_strarray_free(&remotes);
return error;
}
/** That example's entry point */
int lg2_checkout(git_repository *repo, int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
checkout_options opts;
git_repository_state_t state;
git_annotated_commit *checkout_target = NULL;
int err = 0;
const char *path = ".";
/** Parse our command line options */
parse_options(&path, &opts, &args);
/** Make sure we're not about to checkout while something else is going on */
state = git_repository_state(repo);
if (state != GIT_REPOSITORY_STATE_NONE) {
fprintf(stderr, "repository is in unexpected state %d\n", state);
goto cleanup;
}
if (match_arg_separator(&args)) {
/**
* Try to checkout the given path
*/
fprintf(stderr, "unhandled path-based checkout\n");
err = 1;
goto cleanup;
} else {
/**
* Try to resolve a "refish" argument to a target libgit2 can use
*/
if ((err = resolve_refish(&checkout_target, repo, args.argv[args.pos])) < 0 &&
(err = guess_refish(&checkout_target, repo, args.argv[args.pos])) < 0) {
fprintf(stderr, "failed to resolve %s: %s\n", args.argv[args.pos], git_error_last()->message);
goto cleanup;
}
err = perform_checkout_ref(repo, checkout_target, args.argv[args.pos], &opts);
}
cleanup:
git_annotated_commit_free(checkout_target);
return err;
}
+104
View File
@@ -0,0 +1,104 @@
#include "common.h"
typedef struct progress_data {
git_indexer_progress fetch_progress;
size_t completed_steps;
size_t total_steps;
const char *path;
} progress_data;
static void print_progress(const progress_data *pd)
{
int network_percent = pd->fetch_progress.total_objects > 0 ?
(100*pd->fetch_progress.received_objects) / pd->fetch_progress.total_objects :
0;
int index_percent = pd->fetch_progress.total_objects > 0 ?
(100*pd->fetch_progress.indexed_objects) / pd->fetch_progress.total_objects :
0;
int checkout_percent = pd->total_steps > 0
? (int)((100 * pd->completed_steps) / pd->total_steps)
: 0;
size_t kbytes = pd->fetch_progress.received_bytes / 1024;
if (pd->fetch_progress.total_objects &&
pd->fetch_progress.received_objects == pd->fetch_progress.total_objects) {
printf("Resolving deltas %u/%u\r",
pd->fetch_progress.indexed_deltas,
pd->fetch_progress.total_deltas);
} else {
printf("net %3d%% (%4" PRIuZ " kb, %5u/%5u) / idx %3d%% (%5u/%5u) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ")%s\n",
network_percent, kbytes,
pd->fetch_progress.received_objects, pd->fetch_progress.total_objects,
index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects,
checkout_percent,
pd->completed_steps, pd->total_steps,
pd->path);
}
}
static int sideband_progress(const char *str, int len, void *payload)
{
(void)payload; /* unused */
printf("remote: %.*s", len, str);
fflush(stdout);
return 0;
}
static int fetch_progress(const git_indexer_progress *stats, void *payload)
{
progress_data *pd = (progress_data*)payload;
pd->fetch_progress = *stats;
print_progress(pd);
return 0;
}
static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
{
progress_data *pd = (progress_data*)payload;
pd->completed_steps = cur;
pd->total_steps = tot;
pd->path = path;
print_progress(pd);
}
int lg2_clone(git_repository *repo, int argc, char **argv)
{
progress_data pd = {{0}};
git_repository *cloned_repo = NULL;
git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
const char *url = argv[1];
const char *path = argv[2];
int error;
(void)repo; /* unused */
/* Validate args */
if (argc < 3) {
printf ("USAGE: %s <url> <path>\n", argv[0]);
return -1;
}
/* Set up options */
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
checkout_opts.progress_cb = checkout_progress;
checkout_opts.progress_payload = &pd;
clone_opts.checkout_opts = checkout_opts;
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
clone_opts.fetch_opts.callbacks.transfer_progress = &fetch_progress;
clone_opts.fetch_opts.callbacks.credentials = cred_acquire_cb;
clone_opts.fetch_opts.callbacks.payload = &pd;
/* Do the clone */
error = git_clone(&cloned_repo, url, path, &clone_opts);
printf("\n");
if (error != 0) {
const git_error *err = git_error_last();
if (err) printf("ERROR %d: %s\n", err->klass, err->message);
else printf("ERROR %d: no detailed info\n", error);
}
else if (cloned_repo) git_repository_free(cloned_repo);
return error;
}
+260
View File
@@ -0,0 +1,260 @@
/*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
#ifndef _WIN32
# include <unistd.h>
#endif
#include <errno.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 = git_error_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);
}
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_OBJECT_TREE),
"resolving object to tree", treeish);
git_object_free(obj);
}
void *xrealloc(void *oldp, size_t newsz)
{
void *p = realloc(oldp, newsz);
if (p == NULL) {
fprintf(stderr, "Cannot allocate memory, exiting.\n");
exit(1);
}
return p;
}
int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish)
{
git_reference *ref;
git_object *obj;
int err = 0;
assert(commit != NULL);
err = git_reference_dwim(&ref, repo, refish);
if (err == GIT_OK) {
git_annotated_commit_from_ref(commit, repo, ref);
git_reference_free(ref);
return 0;
}
err = git_revparse_single(&obj, repo, refish);
if (err == GIT_OK) {
err = git_annotated_commit_lookup(commit, repo, git_object_id(obj));
git_object_free(obj);
}
return err;
}
static int readline(char **out)
{
int c, error = 0, length = 0, allocated = 0;
char *line = NULL;
errno = 0;
while ((c = getchar()) != EOF) {
if (length == allocated) {
allocated += 16;
if ((line = realloc(line, allocated)) == NULL) {
error = -1;
goto error;
}
}
if (c == '\n')
break;
line[length++] = c;
}
if (errno != 0) {
error = -1;
goto error;
}
line[length] = '\0';
*out = line;
line = NULL;
error = length;
error:
free(line);
return error;
}
static int ask(char **out, const char *prompt, char optional)
{
printf("%s ", prompt);
fflush(stdout);
if (!readline(out) && !optional) {
fprintf(stderr, "Could not read response: %s", strerror(errno));
return -1;
}
return 0;
}
int cred_acquire_cb(git_cred **out,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *payload)
{
char *username = NULL, *password = NULL, *privkey = NULL, *pubkey = NULL;
int error = 1;
UNUSED(url);
UNUSED(payload);
if (username_from_url) {
if ((username = strdup(username_from_url)) == NULL)
goto out;
} else if ((error = ask(&username, "Username:", 0)) < 0) {
goto out;
}
if (allowed_types & GIT_CREDTYPE_SSH_KEY) {
int n;
if ((error = ask(&privkey, "SSH Key:", 0)) < 0 ||
(error = ask(&password, "Password:", 1)) < 0)
goto out;
if ((n = snprintf(NULL, 0, "%s.pub", privkey)) < 0 ||
(pubkey = malloc(n + 1)) == NULL ||
(n = snprintf(pubkey, n + 1, "%s.pub", privkey)) < 0)
goto out;
error = git_cred_ssh_key_new(out, username, pubkey, privkey, password);
} else if (allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT) {
if ((error = ask(&password, "Password:", 1)) < 0)
goto out;
error = git_cred_userpass_plaintext_new(out, username, password);
} else if (allowed_types & GIT_CREDTYPE_USERNAME) {
error = git_cred_username_new(out, username);
}
out:
free(username);
free(password);
free(privkey);
free(pubkey);
return error;
}
char *read_file(const char *path)
{
ssize_t total = 0;
char *buf = NULL;
struct stat st;
int fd = -1;
if ((fd = open(path, O_RDONLY)) < 0 || fstat(fd, &st) < 0)
goto out;
if ((buf = malloc(st.st_size + 1)) == NULL)
goto out;
while (total < st.st_size) {
ssize_t bytes = read(fd, buf + total, st.st_size - total);
if (bytes <= 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
free(buf);
buf = NULL;
goto out;
}
total += bytes;
}
buf[total] = '\0';
out:
if (fd >= 0)
close(fd);
return buf;
}
+134
View File
@@ -0,0 +1,134 @@
/*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#ifndef INCLUDE_examples_common_h__
#define INCLUDE_examples_common_h__
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <git2.h>
#include <fcntl.h>
#ifdef _WIN32
# include <io.h>
# include <Windows.h>
# define open _open
# define read _read
# define close _close
# define ssize_t int
# define sleep(a) Sleep(a * 1000)
#else
# include <unistd.h>
#endif
#ifndef PRIuZ
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)
# define PRIuZ "Iu"
#else
# define PRIuZ "zu"
#endif
#endif
#ifdef _MSC_VER
#define snprintf sprintf_s
#define strcasecmp strcmpi
#endif
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*x))
#define UNUSED(x) (void)(x)
#include "args.h"
extern int lg2_add(git_repository *repo, int argc, char **argv);
extern int lg2_blame(git_repository *repo, int argc, char **argv);
extern int lg2_cat_file(git_repository *repo, int argc, char **argv);
extern int lg2_checkout(git_repository *repo, int argc, char **argv);
extern int lg2_clone(git_repository *repo, int argc, char **argv);
extern int lg2_config(git_repository *repo, int argc, char **argv);
extern int lg2_describe(git_repository *repo, int argc, char **argv);
extern int lg2_diff(git_repository *repo, int argc, char **argv);
extern int lg2_fetch(git_repository *repo, int argc, char **argv);
extern int lg2_for_each_ref(git_repository *repo, int argc, char **argv);
extern int lg2_general(git_repository *repo, int argc, char **argv);
extern int lg2_index_pack(git_repository *repo, int argc, char **argv);
extern int lg2_init(git_repository *repo, int argc, char **argv);
extern int lg2_log(git_repository *repo, int argc, char **argv);
extern int lg2_ls_files(git_repository *repo, int argc, char **argv);
extern int lg2_ls_remote(git_repository *repo, int argc, char **argv);
extern int lg2_merge(git_repository *repo, int argc, char **argv);
extern int lg2_remote(git_repository *repo, int argc, char **argv);
extern int lg2_rev_list(git_repository *repo, int argc, char **argv);
extern int lg2_rev_parse(git_repository *repo, int argc, char **argv);
extern int lg2_show_index(git_repository *repo, int argc, char **argv);
extern int lg2_stash(git_repository *repo, int argc, char **argv);
extern int lg2_status(git_repository *repo, int argc, char **argv);
extern int lg2_tag(git_repository *repo, int argc, char **argv);
/**
* 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);
/**
* Read a file into a buffer
*
* @param path The path to the file that shall be read
* @return NUL-terminated buffer if the file was successfully read, NULL-pointer otherwise
*/
extern char *read_file(const char *path);
/**
* Exit the program, printing error to stderr
*/
extern void fatal(const char *message, const char *extra);
/**
* 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);
/**
* A realloc that exits on failure
*/
extern void *xrealloc(void *oldp, size_t newsz);
/**
* Convert a refish to an annotated commit.
*/
extern int resolve_refish(git_annotated_commit **commit, git_repository *repo, const char *refish);
/**
* Acquire credentials via command line
*/
extern int cred_acquire_cb(git_cred **out,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *payload);
#endif
+62
View File
@@ -0,0 +1,62 @@
/*
* libgit2 "config" example - shows how to use the config API
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
static int config_get(git_config *cfg, const char *key)
{
git_config_entry *entry;
int error;
if ((error = git_config_get_entry(&entry, cfg, key)) < 0) {
if (error != GIT_ENOTFOUND)
printf("Unable to get configuration: %s\n", git_error_last()->message);
return 1;
}
puts(entry->value);
return 0;
}
static int config_set(git_config *cfg, const char *key, const char *value)
{
if (git_config_set_string(cfg, key, value) < 0) {
printf("Unable to set configuration: %s\n", git_error_last()->message);
return 1;
}
return 0;
}
int lg2_config(git_repository *repo, int argc, char **argv)
{
git_config *cfg;
int error;
if ((error = git_repository_config(&cfg, repo)) < 0) {
printf("Unable to obtain repository config: %s\n", git_error_last()->message);
goto out;
}
if (argc == 2) {
error = config_get(cfg, argv[1]);
} else if (argc == 3) {
error = config_set(cfg, argv[1], argv[2]);
} else {
printf("USAGE: %s config <KEY> [<VALUE>]\n", argv[0]);
error = 1;
}
out:
return error;
}
+162
View File
@@ -0,0 +1,162 @@
/*
* libgit2 "describe" example - shows how to describe commits
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* The following example partially reimplements the `git describe` command
* and some of its options.
*
* These commands should work:
* - Describe HEAD with default options (`describe`)
* - Describe specified revision (`describe master~2`)
* - Describe specified revisions (`describe master~2 HEAD~3`)
* - Describe HEAD with dirty state suffix (`describe --dirty=*`)
* - Describe consider all refs (`describe --all master`)
* - Describe consider lightweight tags (`describe --tags temp-tag`)
* - Describe show non-default abbreviated size (`describe --abbrev=10`)
* - Describe always output the long format if matches a tag (`describe --long v1.0`)
* - Describe consider only tags of specified pattern (`describe --match v*-release`)
* - Describe show the fallback result (`describe --always`)
* - Describe follow only the first parent commit (`describe --first-parent`)
*
* The command line parsing logic is simplified and doesn't handle
* all of the use cases.
*/
/** describe_options represents the parsed command line options */
struct describe_options {
const char **commits;
size_t commit_count;
git_describe_options describe_options;
git_describe_format_options format_options;
};
static void opts_add_commit(struct describe_options *opts, const char *commit)
{
size_t sz;
assert(opts != NULL);
sz = ++opts->commit_count * sizeof(opts->commits[0]);
opts->commits = xrealloc((void *) opts->commits, sz);
opts->commits[opts->commit_count - 1] = commit;
}
static void do_describe_single(git_repository *repo, struct describe_options *opts, const char *rev)
{
git_object *commit;
git_describe_result *describe_result;
git_buf buf = { 0 };
if (rev) {
check_lg2(git_revparse_single(&commit, repo, rev),
"Failed to lookup rev", rev);
check_lg2(git_describe_commit(&describe_result, commit, &opts->describe_options),
"Failed to describe rev", rev);
}
else
check_lg2(git_describe_workdir(&describe_result, repo, &opts->describe_options),
"Failed to describe workdir", NULL);
check_lg2(git_describe_format(&buf, describe_result, &opts->format_options),
"Failed to format describe rev", rev);
printf("%s\n", buf.ptr);
}
static void do_describe(git_repository *repo, struct describe_options *opts)
{
if (opts->commit_count == 0)
do_describe_single(repo, opts, NULL);
else
{
size_t i;
for (i = 0; i < opts->commit_count; i++)
do_describe_single(repo, opts, opts->commits[i]);
}
}
static void print_usage(void)
{
fprintf(stderr, "usage: see `git help describe`\n");
exit(1);
}
/** Parse command line arguments */
static void parse_options(struct describe_options *opts, int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *curr = argv[args.pos];
if (curr[0] != '-') {
opts_add_commit(opts, curr);
} else if (!strcmp(curr, "--all")) {
opts->describe_options.describe_strategy = GIT_DESCRIBE_ALL;
} else if (!strcmp(curr, "--tags")) {
opts->describe_options.describe_strategy = GIT_DESCRIBE_TAGS;
} else if (!strcmp(curr, "--exact-match")) {
opts->describe_options.max_candidates_tags = 0;
} else if (!strcmp(curr, "--long")) {
opts->format_options.always_use_long_format = 1;
} else if (!strcmp(curr, "--always")) {
opts->describe_options.show_commit_oid_as_fallback = 1;
} else if (!strcmp(curr, "--first-parent")) {
opts->describe_options.only_follow_first_parent = 1;
} else if (optional_str_arg(&opts->format_options.dirty_suffix, &args, "--dirty", "-dirty")) {
} else if (match_int_arg((int *)&opts->format_options.abbreviated_size, &args, "--abbrev", 0)) {
} else if (match_int_arg((int *)&opts->describe_options.max_candidates_tags, &args, "--candidates", 0)) {
} else if (match_str_arg(&opts->describe_options.pattern, &args, "--match")) {
} else {
print_usage();
}
}
if (opts->commit_count > 0) {
if (opts->format_options.dirty_suffix)
fatal("--dirty is incompatible with commit-ishes", NULL);
}
else {
if (!opts->format_options.dirty_suffix || !opts->format_options.dirty_suffix[0]) {
opts_add_commit(opts, "HEAD");
}
}
}
/** Initialize describe_options struct */
static void describe_options_init(struct describe_options *opts)
{
memset(opts, 0, sizeof(*opts));
opts->commits = NULL;
opts->commit_count = 0;
git_describe_options_init(&opts->describe_options, GIT_DESCRIBE_OPTIONS_VERSION);
git_describe_format_options_init(&opts->format_options, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
}
int lg2_describe(git_repository *repo, int argc, char **argv)
{
struct describe_options opts;
describe_options_init(&opts);
parse_options(&opts, argc, argv);
do_describe(repo, &opts);
return 0;
}
+369
View File
@@ -0,0 +1,369 @@
/*
* libgit2 "diff" example - shows how to use the diff API
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates the use of the libgit2 diff APIs to
* create `git_diff` objects and display them, emulating a number of
* core Git `diff` command line options.
*
* This covers on a portion of the core Git diff options and doesn't
* have particularly good error handling, but it should show most of
* the core libgit2 diff APIs, including various types of diffs and
* how to do renaming detection and patch formatting.
*/
static const char *colors[] = {
"\033[m", /* reset */
"\033[1m", /* bold */
"\033[31m", /* red */
"\033[32m", /* green */
"\033[36m" /* cyan */
};
enum {
OUTPUT_DIFF = (1 << 0),
OUTPUT_STAT = (1 << 1),
OUTPUT_SHORTSTAT = (1 << 2),
OUTPUT_NUMSTAT = (1 << 3),
OUTPUT_SUMMARY = (1 << 4)
};
enum {
CACHE_NORMAL = 0,
CACHE_ONLY = 1,
CACHE_NONE = 2
};
/** The 'diff_options' struct captures all the various parsed command line options. */
struct diff_options {
git_diff_options diffopts;
git_diff_find_options findopts;
int color;
int no_index;
int cache;
int output;
git_diff_format_t format;
const char *treeish1;
const char *treeish2;
const char *dir;
};
/** These functions are implemented at the end */
static void usage(const char *message, const char *arg);
static void parse_opts(struct diff_options *o, int argc, char *argv[]);
static int color_printer(
const git_diff_delta*, const git_diff_hunk*, const git_diff_line*, void*);
static void diff_print_stats(git_diff *diff, struct diff_options *o);
static void compute_diff_no_index(git_diff **diff, struct diff_options *o);
int lg2_diff(git_repository *repo, int argc, char *argv[])
{
git_tree *t1 = NULL, *t2 = NULL;
git_diff *diff;
struct diff_options o = {
GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
-1, -1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
};
parse_opts(&o, argc, argv);
/**
* Possible argument patterns:
*
* * &lt;sha1&gt; &lt;sha2&gt;
* * &lt;sha1&gt; --cached
* * &lt;sha1&gt;
* * --cached
* * --nocache (don't use index data in diff at all)
* * --no-index &lt;file1&gt; &lt;file2&gt;
* * nothing
*
* Currently ranged arguments like &lt;sha1&gt;..&lt;sha2&gt; and &lt;sha1&gt;...&lt;sha2&gt;
* are not supported in this example
*/
if (o.no_index >= 0) {
compute_diff_no_index(&diff, &o);
} else {
if (o.treeish1)
treeish_to_tree(&t1, repo, o.treeish1);
if (o.treeish2)
treeish_to_tree(&t2, repo, o.treeish2);
if (t1 && t2)
check_lg2(
git_diff_tree_to_tree(&diff, repo, t1, t2, &o.diffopts),
"diff trees", NULL);
else if (o.cache != CACHE_NORMAL) {
if (!t1)
treeish_to_tree(&t1, repo, "HEAD");
if (o.cache == CACHE_NONE)
check_lg2(
git_diff_tree_to_workdir(&diff, repo, t1, &o.diffopts),
"diff tree to working directory", NULL);
else
check_lg2(
git_diff_tree_to_index(&diff, repo, t1, NULL, &o.diffopts),
"diff tree to index", NULL);
}
else if (t1)
check_lg2(
git_diff_tree_to_workdir_with_index(&diff, repo, t1, &o.diffopts),
"diff tree to working directory", NULL);
else
check_lg2(
git_diff_index_to_workdir(&diff, repo, NULL, &o.diffopts),
"diff index to working directory", NULL);
/** Apply rename and copy detection if requested. */
if ((o.findopts.flags & GIT_DIFF_FIND_ALL) != 0)
check_lg2(
git_diff_find_similar(diff, &o.findopts),
"finding renames and copies", NULL);
}
/** Generate simple output using libgit2 display helper. */
if (!o.output)
o.output = OUTPUT_DIFF;
if (o.output != OUTPUT_DIFF)
diff_print_stats(diff, &o);
if ((o.output & OUTPUT_DIFF) != 0) {
if (o.color >= 0)
fputs(colors[0], stdout);
check_lg2(
git_diff_print(diff, o.format, color_printer, &o.color),
"displaying diff", NULL);
if (o.color >= 0)
fputs(colors[0], stdout);
}
/** Cleanup before exiting. */
git_diff_free(diff);
git_tree_free(t1);
git_tree_free(t2);
return 0;
}
static void compute_diff_no_index(git_diff **diff, struct diff_options *o) {
git_patch *patch = NULL;
char *file1_str = NULL;
char *file2_str = NULL;
git_buf buf = {0};
if (!o->treeish1 || !o->treeish2) {
usage("two files should be provided as arguments", NULL);
}
file1_str = read_file(o->treeish1);
if (file1_str == NULL) {
usage("file cannot be read", o->treeish1);
}
file2_str = read_file(o->treeish2);
if (file2_str == NULL) {
usage("file cannot be read", o->treeish2);
}
check_lg2(
git_patch_from_buffers(&patch, file1_str, strlen(file1_str), o->treeish1, file2_str, strlen(file2_str), o->treeish2, &o->diffopts),
"patch buffers", NULL);
check_lg2(
git_patch_to_buf(&buf, patch),
"patch to buf", NULL);
check_lg2(
git_diff_from_buffer(diff, buf.ptr, buf.size),
"diff from patch", NULL);
git_patch_free(patch);
git_buf_dispose(&buf);
free(file1_str);
free(file2_str);
}
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: diff [<tree-oid> [<tree-oid>]]\n");
exit(1);
}
/** This implements very rudimentary colorized output. */
static int color_printer(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *data)
{
int *last_color = data, color = 0;
(void)delta; (void)hunk;
if (*last_color >= 0) {
switch (line->origin) {
case GIT_DIFF_LINE_ADDITION: color = 3; break;
case GIT_DIFF_LINE_DELETION: color = 2; break;
case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
default: break;
}
if (color != *last_color) {
if (*last_color == 1 || color == 1)
fputs(colors[0], stdout);
fputs(colors[color], stdout);
*last_color = color;
}
}
return diff_output(delta, hunk, line, stdout);
}
/** Parse arguments as copied from git-diff. */
static void parse_opts(struct diff_options *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *a = argv[args.pos];
if (a[0] != '-') {
if (o->treeish1 == NULL)
o->treeish1 = a;
else if (o->treeish2 == NULL)
o->treeish2 = a;
else
usage("Only one or two tree identifiers can be provided", NULL);
}
else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
!strcmp(a, "--patch")) {
o->output |= OUTPUT_DIFF;
o->format = GIT_DIFF_FORMAT_PATCH;
}
else if (!strcmp(a, "--cached")) {
o->cache = CACHE_ONLY;
if (o->no_index >= 0) usage("--cached and --no-index are incompatible", NULL);
} else if (!strcmp(a, "--nocache"))
o->cache = CACHE_NONE;
else if (!strcmp(a, "--name-only") || !strcmp(a, "--format=name"))
o->format = GIT_DIFF_FORMAT_NAME_ONLY;
else if (!strcmp(a, "--name-status") ||
!strcmp(a, "--format=name-status"))
o->format = GIT_DIFF_FORMAT_NAME_STATUS;
else if (!strcmp(a, "--raw") || !strcmp(a, "--format=raw"))
o->format = GIT_DIFF_FORMAT_RAW;
else if (!strcmp(a, "--format=diff-index")) {
o->format = GIT_DIFF_FORMAT_RAW;
o->diffopts.id_abbrev = 40;
}
else if (!strcmp(a, "--no-index")) {
o->no_index = 0;
if (o->cache == CACHE_ONLY) usage("--cached and --no-index are incompatible", NULL);
} else if (!strcmp(a, "--color"))
o->color = 0;
else if (!strcmp(a, "--no-color"))
o->color = -1;
else if (!strcmp(a, "-R"))
o->diffopts.flags |= GIT_DIFF_REVERSE;
else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
o->diffopts.flags |= GIT_DIFF_FORCE_TEXT;
else if (!strcmp(a, "--ignore-space-at-eol"))
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
o->diffopts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
else if (!strcmp(a, "--ignored"))
o->diffopts.flags |= GIT_DIFF_INCLUDE_IGNORED;
else if (!strcmp(a, "--untracked"))
o->diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
else if (!strcmp(a, "--patience"))
o->diffopts.flags |= GIT_DIFF_PATIENCE;
else if (!strcmp(a, "--minimal"))
o->diffopts.flags |= GIT_DIFF_MINIMAL;
else if (!strcmp(a, "--stat"))
o->output |= OUTPUT_STAT;
else if (!strcmp(a, "--numstat"))
o->output |= OUTPUT_NUMSTAT;
else if (!strcmp(a, "--shortstat"))
o->output |= OUTPUT_SHORTSTAT;
else if (!strcmp(a, "--summary"))
o->output |= OUTPUT_SUMMARY;
else if (match_uint16_arg(
&o->findopts.rename_threshold, &args, "-M") ||
match_uint16_arg(
&o->findopts.rename_threshold, &args, "--find-renames"))
o->findopts.flags |= GIT_DIFF_FIND_RENAMES;
else if (match_uint16_arg(
&o->findopts.copy_threshold, &args, "-C") ||
match_uint16_arg(
&o->findopts.copy_threshold, &args, "--find-copies"))
o->findopts.flags |= GIT_DIFF_FIND_COPIES;
else if (!strcmp(a, "--find-copies-harder"))
o->findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites"))
/* TODO: parse thresholds */
o->findopts.flags |= GIT_DIFF_FIND_REWRITES;
else if (!match_uint32_arg(
&o->diffopts.context_lines, &args, "-U") &&
!match_uint32_arg(
&o->diffopts.context_lines, &args, "--unified") &&
!match_uint32_arg(
&o->diffopts.interhunk_lines, &args, "--inter-hunk-context") &&
!match_uint16_arg(
&o->diffopts.id_abbrev, &args, "--abbrev") &&
!match_str_arg(&o->diffopts.old_prefix, &args, "--src-prefix") &&
!match_str_arg(&o->diffopts.new_prefix, &args, "--dst-prefix") &&
!match_str_arg(&o->dir, &args, "--git-dir"))
usage("Unknown command line argument", a);
}
}
/** Display diff output with "--stat", "--numstat", or "--shortstat" */
static void diff_print_stats(git_diff *diff, struct diff_options *o)
{
git_diff_stats *stats;
git_buf b = GIT_BUF_INIT_CONST(NULL, 0);
git_diff_stats_format_t format = 0;
check_lg2(
git_diff_get_stats(&stats, diff), "generating stats for diff", NULL);
if (o->output & OUTPUT_STAT)
format |= GIT_DIFF_STATS_FULL;
if (o->output & OUTPUT_SHORTSTAT)
format |= GIT_DIFF_STATS_SHORT;
if (o->output & OUTPUT_NUMSTAT)
format |= GIT_DIFF_STATS_NUMBER;
if (o->output & OUTPUT_SUMMARY)
format |= GIT_DIFF_STATS_INCLUDE_SUMMARY;
check_lg2(
git_diff_stats_to_buf(&b, stats, format, 80), "formatting stats", NULL);
fputs(b.ptr, stdout);
git_buf_dispose(&b);
git_diff_stats_free(stats);
}
+109
View File
@@ -0,0 +1,109 @@
#include "common.h"
static int progress_cb(const char *str, int len, void *data)
{
(void)data;
printf("remote: %.*s", len, str);
fflush(stdout); /* We don't have the \n to force the flush */
return 0;
}
/**
* This function gets called for each remote-tracking branch that gets
* updated. The message we output depends on whether it's a new one or
* an update.
*/
static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
{
char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1];
(void)data;
git_oid_fmt(b_str, b);
b_str[GIT_OID_HEXSZ] = '\0';
if (git_oid_is_zero(a)) {
printf("[new] %.20s %s\n", b_str, refname);
} else {
git_oid_fmt(a_str, a);
a_str[GIT_OID_HEXSZ] = '\0';
printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname);
}
return 0;
}
/**
* This gets called during the download and indexing. Here we show
* processed and total objects in the pack and the amount of received
* data. Most frontends will probably want to show a percentage and
* the download rate.
*/
static int transfer_progress_cb(const git_indexer_progress *stats, void *payload)
{
(void)payload;
if (stats->received_objects == stats->total_objects) {
printf("Resolving deltas %u/%u\r",
stats->indexed_deltas, stats->total_deltas);
} else if (stats->total_objects > 0) {
printf("Received %u/%u objects (%u) in %" PRIuZ " bytes\r",
stats->received_objects, stats->total_objects,
stats->indexed_objects, stats->received_bytes);
}
return 0;
}
/** Entry point for this command */
int lg2_fetch(git_repository *repo, int argc, char **argv)
{
git_remote *remote = NULL;
const git_indexer_progress *stats;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
if (argc < 2) {
fprintf(stderr, "usage: %s fetch <repo>\n", argv[-1]);
return EXIT_FAILURE;
}
/* Figure out whether it's a named remote or a URL */
printf("Fetching %s for repo %p\n", argv[1], repo);
if (git_remote_lookup(&remote, repo, argv[1]) < 0)
if (git_remote_create_anonymous(&remote, repo, argv[1]) < 0)
goto on_error;
/* Set up the callbacks (only update_tips for now) */
fetch_opts.callbacks.update_tips = &update_cb;
fetch_opts.callbacks.sideband_progress = &progress_cb;
fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
fetch_opts.callbacks.credentials = cred_acquire_cb;
/**
* Perform the fetch with the configured refspecs from the
* config. Update the reflog for the updated references with
* "fetch".
*/
if (git_remote_fetch(remote, NULL, &fetch_opts, "fetch") < 0)
goto on_error;
/**
* If there are local objects (we got a thin pack), then tell
* the user how many objects we saved from having to cross the
* network.
*/
stats = git_remote_stats(remote);
if (stats->local_objects > 0) {
printf("\rReceived %u/%u objects in %" PRIuZ " bytes (used %u local objects)\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes, stats->local_objects);
} else{
printf("\rReceived %u/%u objects in %" PRIuZ "bytes\n",
stats->indexed_objects, stats->total_objects, stats->received_bytes);
}
git_remote_free(remote);
return 0;
on_error:
git_remote_free(remote);
return -1;
}
+44
View File
@@ -0,0 +1,44 @@
#include <git2.h>
#include "common.h"
static int show_ref(git_reference *ref, void *data)
{
git_repository *repo = data;
git_reference *resolved = NULL;
char hex[GIT_OID_HEXSZ+1];
const git_oid *oid;
git_object *obj;
if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC)
check_lg2(git_reference_resolve(&resolved, ref),
"Unable to resolve symbolic reference",
git_reference_name(ref));
oid = git_reference_target(resolved ? resolved : ref);
git_oid_fmt(hex, oid);
hex[GIT_OID_HEXSZ] = 0;
check_lg2(git_object_lookup(&obj, repo, oid, GIT_OBJECT_ANY),
"Unable to lookup object", hex);
printf("%s %-6s\t%s\n",
hex,
git_object_type2string(git_object_type(obj)),
git_reference_name(ref));
if (resolved)
git_reference_free(resolved);
return 0;
}
int lg2_for_each_ref(git_repository *repo, int argc, char **argv)
{
UNUSED(argv);
if (argc != 1)
fatal("Sorry, no for-each-ref options supported yet", NULL);
check_lg2(git_reference_foreach(repo, show_ref, repo),
"Could not iterate over references", NULL);
return 0;
}
+774
View File
@@ -0,0 +1,774 @@
/*
* libgit2 "general" example - shows basic libgit2 concepts
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
/**
* [**libgit2**][lg] is a portable, pure C implementation of the Git core
* methods provided as a re-entrant linkable library with a solid API,
* allowing you to write native speed custom Git applications in any
* language which supports C bindings.
*
* This file is an example of using that API in a real, compilable C file.
* As the API is updated, this file will be updated to demonstrate the new
* functionality.
*
* If you're trying to write something in C using [libgit2][lg], you should
* also check out the generated [API documentation][ap]. We try to link to
* the relevant sections of the API docs in each section in this file.
*
* **libgit2** (for the most part) only implements the core plumbing
* functions, not really the higher level porcelain stuff. For a primer on
* Git Internals that you will need to know to work with Git at this level,
* check out [Chapter 10][pg] of the Pro Git book.
*
* [lg]: http://libgit2.github.com
* [ap]: http://libgit2.github.com/libgit2
* [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain
*/
#include "common.h"
/**
* ### Includes
*
* Including the `git2.h` header will include all the other libgit2 headers
* that you need. It should be the only thing you need to include in order
* to compile properly and get all the libgit2 API.
*/
#include "git2.h"
static void oid_parsing(git_oid *out);
static void object_database(git_repository *repo, git_oid *oid);
static void commit_writing(git_repository *repo);
static void commit_parsing(git_repository *repo);
static void tag_parsing(git_repository *repo);
static void tree_parsing(git_repository *repo);
static void blob_parsing(git_repository *repo);
static void revwalking(git_repository *repo);
static void index_walking(git_repository *repo);
static void reference_listing(git_repository *repo);
static void config_files(const char *repo_path, git_repository *repo);
/**
* Almost all libgit2 functions return 0 on success or negative on error.
* This is not production quality error checking, but should be sufficient
* as an example.
*/
static void check_error(int error_code, const char *action)
{
const git_error *error = git_error_last();
if (!error_code)
return;
printf("Error %d %s - %s\n", error_code, action,
(error && error->message) ? error->message : "???");
exit(1);
}
int lg2_general(git_repository *repo, int argc, char** argv)
{
int error;
git_oid oid;
char *repo_path;
/**
* Initialize the library, this will set up any global state which libgit2 needs
* including threading and crypto
*/
git_libgit2_init();
/**
* ### Opening the Repository
*
* There are a couple of methods for opening a repository, this being the
* simplest. There are also [methods][me] for specifying the index file
* and work tree locations, here we assume they are in the normal places.
*
* (Try running this program against tests/resources/testrepo.git.)
*
* [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository
*/
repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git";
error = git_repository_open(&repo, repo_path);
check_error(error, "opening repository");
oid_parsing(&oid);
object_database(repo, &oid);
commit_writing(repo);
commit_parsing(repo);
tag_parsing(repo);
tree_parsing(repo);
blob_parsing(repo);
revwalking(repo);
index_walking(repo);
reference_listing(repo);
config_files(repo_path, repo);
/**
* Finally, when you're done with the repository, you can free it as well.
*/
git_repository_free(repo);
return 0;
}
/**
* ### SHA-1 Value Conversions
*/
static void oid_parsing(git_oid *oid)
{
char out[GIT_OID_HEXSZ+1];
char hex[] = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045";
printf("*Hex to Raw*\n");
/**
* For our first example, we will convert a 40 character hex value to the
* 20 byte raw SHA1 value.
*
* The `git_oid` is the structure that keeps the SHA value. We will use
* this throughout the example for storing the value of the current SHA
* key we're working with.
*/
git_oid_fromstr(oid, hex);
/*
* Once we've converted the string into the oid value, we can get the raw
* value of the SHA by accessing `oid.id`
*
* Next we will convert the 20 byte raw SHA1 value to a human readable 40
* char hex value.
*/
printf("\n*Raw to Hex*\n");
out[GIT_OID_HEXSZ] = '\0';
/**
* If you have a oid, you can easily get the hex value of the SHA as well.
*/
git_oid_fmt(out, oid);
/**
* If you have a oid, you can easily get the hex value of the SHA as well.
*/
git_oid_fmt(out, oid);
printf("SHA hex string: %s\n", out);
}
/**
* ### Working with the Object Database
*
* **libgit2** provides [direct access][odb] to the object database. The
* object database is where the actual objects are stored in Git. For
* working with raw objects, we'll need to get this structure from the
* repository.
*
* [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb
*/
static void object_database(git_repository *repo, git_oid *oid)
{
char oid_hex[GIT_OID_HEXSZ+1] = { 0 };
const unsigned char *data;
const char *str_type;
int error;
git_odb_object *obj;
git_odb *odb;
git_object_t otype;
git_repository_odb(&odb, repo);
/**
* #### Raw Object Reading
*/
printf("\n*Raw Object Read*\n");
/**
* We can read raw objects directly from the object database if we have
* the oid (SHA) of the object. This allows us to access objects without
* knowing their type and inspect the raw bytes unparsed.
*/
error = git_odb_read(&obj, odb, oid);
check_error(error, "finding object in repository");
/**
* A raw object only has three properties - the type (commit, blob, tree
* or tag), the size of the raw data and the raw, unparsed data itself.
* For a commit or tag, that raw data is human readable plain ASCII
* text. For a blob it is just file contents, so it could be text or
* binary data. For a tree it is a special binary format, so it's unlikely
* to be hugely helpful as a raw object.
*/
data = (const unsigned char *)git_odb_object_data(obj);
otype = git_odb_object_type(obj);
/**
* We provide methods to convert from the object type which is an enum, to
* a string representation of that value (and vice-versa).
*/
str_type = git_object_type2string(otype);
printf("object length and type: %d, %s\nobject data: %s\n",
(int)git_odb_object_size(obj),
str_type, data);
/**
* For proper memory management, close the object when you are done with
* it or it will leak memory.
*/
git_odb_object_free(obj);
/**
* #### Raw Object Writing
*/
printf("\n*Raw Object Write*\n");
/**
* You can also write raw object data to Git. This is pretty cool because
* it gives you direct access to the key/value properties of Git. Here
* we'll write a new blob object that just contains a simple string.
* Notice that we have to specify the object type as the `git_otype` enum.
*/
git_odb_write(oid, odb, "test data", sizeof("test data") - 1, GIT_OBJECT_BLOB);
/**
* Now that we've written the object, we can check out what SHA1 was
* generated when the object was written to our database.
*/
git_oid_fmt(oid_hex, oid);
printf("Written Object: %s\n", oid_hex);
/**
* Free the object database after usage.
*/
git_odb_free(odb);
}
/**
* #### Writing Commits
*
* libgit2 provides a couple of methods to create commit objects easily as
* well. There are four different create signatures, we'll just show one
* of them here. You can read about the other ones in the [commit API
* docs][cd].
*
* [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit
*/
static void commit_writing(git_repository *repo)
{
git_oid tree_id, parent_id, commit_id;
git_tree *tree;
git_commit *parent;
git_signature *author, *committer;
char oid_hex[GIT_OID_HEXSZ+1] = { 0 };
printf("\n*Commit Writing*\n");
/**
* Creating signatures for an authoring identity and time is simple. You
* will need to do this to specify who created a commit and when. Default
* values for the name and email should be found in the `user.name` and
* `user.email` configuration options. See the `config` section of this
* example file to see how to access config values.
*/
git_signature_new(&author,
"Scott Chacon", "schacon@gmail.com", 123456789, 60);
git_signature_new(&committer,
"Scott A Chacon", "scott@github.com", 987654321, 90);
/**
* Commit objects need a tree to point to and optionally one or more
* parents. Here we're creating oid objects to create the commit with,
* but you can also use
*/
git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1");
git_tree_lookup(&tree, repo, &tree_id);
git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
git_commit_lookup(&parent, repo, &parent_id);
/**
* Here we actually create the commit object with a single call with all
* the values we need to create the commit. The SHA key is written to the
* `commit_id` variable here.
*/
git_commit_create_v(
&commit_id, /* out id */
repo,
NULL, /* do not update the HEAD */
author,
committer,
NULL, /* use default message encoding */
"example commit",
tree,
1, parent);
/**
* Now we can take a look at the commit SHA we've generated.
*/
git_oid_fmt(oid_hex, &commit_id);
printf("New Commit: %s\n", oid_hex);
/**
* Free all objects used in the meanwhile.
*/
git_tree_free(tree);
git_commit_free(parent);
git_signature_free(author);
git_signature_free(committer);
}
/**
* ### Object Parsing
*
* libgit2 has methods to parse every object type in Git so you don't have
* to work directly with the raw data. This is much faster and simpler
* than trying to deal with the raw data yourself.
*/
/**
* #### Commit Parsing
*
* [Parsing commit objects][pco] is simple and gives you access to all the
* data in the commit - the author (name, email, datetime), committer
* (same), tree, message, encoding and parent(s).
*
* [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit
*/
static void commit_parsing(git_repository *repo)
{
const git_signature *author, *cmtter;
git_commit *commit, *parent;
git_oid oid;
char oid_hex[GIT_OID_HEXSZ+1];
const char *message;
unsigned int parents, p;
int error;
time_t time;
printf("\n*Commit Parsing*\n");
git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479");
error = git_commit_lookup(&commit, repo, &oid);
check_error(error, "looking up commit");
/**
* Each of the properties of the commit object are accessible via methods,
* including commonly needed variations, such as `git_commit_time` which
* returns the author time and `git_commit_message` which gives you the
* commit message (as a NUL-terminated string).
*/
message = git_commit_message(commit);
author = git_commit_author(commit);
cmtter = git_commit_committer(commit);
time = git_commit_time(commit);
/**
* The author and committer methods return [git_signature] structures,
* which give you name, email and `when`, which is a `git_time` structure,
* giving you a timestamp and timezone offset.
*/
printf("Author: %s (%s)\nCommitter: %s (%s)\nDate: %s\nMessage: %s\n",
author->name, author->email,
cmtter->name, cmtter->email,
ctime(&time), message);
/**
* Commits can have zero or more parents. The first (root) commit will
* have no parents, most commits will have one (i.e. the commit it was
* based on) and merge commits will have two or more. Commits can
* technically have any number, though it's rare to have more than two.
*/
parents = git_commit_parentcount(commit);
for (p = 0;p < parents;p++) {
memset(oid_hex, 0, sizeof(oid_hex));
git_commit_parent(&parent, commit, p);
git_oid_fmt(oid_hex, git_commit_id(parent));
printf("Parent: %s\n", oid_hex);
git_commit_free(parent);
}
git_commit_free(commit);
}
/**
* #### Tag Parsing
*
* You can parse and create tags with the [tag management API][tm], which
* functions very similarly to the commit lookup, parsing and creation
* methods, since the objects themselves are very similar.
*
* [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag
*/
static void tag_parsing(git_repository *repo)
{
git_commit *commit;
git_object_t type;
git_tag *tag;
git_oid oid;
const char *name, *message;
int error;
printf("\n*Tag Parsing*\n");
/**
* We create an oid for the tag object if we know the SHA and look it up
* the same way that we would a commit (or any other object).
*/
git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1");
error = git_tag_lookup(&tag, repo, &oid);
check_error(error, "looking up tag");
/**
* Now that we have the tag object, we can extract the information it
* generally contains: the target (usually a commit object), the type of
* the target object (usually 'commit'), the name ('v1.0'), the tagger (a
* git_signature - name, email, timestamp), and the tag message.
*/
git_tag_target((git_object **)&commit, tag);
name = git_tag_name(tag); /* "test" */
type = git_tag_target_type(tag); /* GIT_OBJECT_COMMIT (object_t enum) */
message = git_tag_message(tag); /* "tag message\n" */
printf("Tag Name: %s\nTag Type: %s\nTag Message: %s\n",
name, git_object_type2string(type), message);
/**
* Free both the commit and tag after usage.
*/
git_commit_free(commit);
git_tag_free(tag);
}
/**
* #### Tree Parsing
*
* [Tree parsing][tp] is a bit different than the other objects, in that
* we have a subtype which is the tree entry. This is not an actual
* object type in Git, but a useful structure for parsing and traversing
* tree entries.
*
* [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree
*/
static void tree_parsing(git_repository *repo)
{
const git_tree_entry *entry;
size_t cnt;
git_object *obj;
git_tree *tree;
git_oid oid;
printf("\n*Tree Parsing*\n");
/**
* Create the oid and lookup the tree object just like the other objects.
*/
git_oid_fromstr(&oid, "f60079018b664e4e79329a7ef9559c8d9e0378d1");
git_tree_lookup(&tree, repo, &oid);
/**
* Getting the count of entries in the tree so you can iterate over them
* if you want to.
*/
cnt = git_tree_entrycount(tree); /* 2 */
printf("tree entries: %d\n", (int) cnt);
entry = git_tree_entry_byindex(tree, 0);
printf("Entry name: %s\n", git_tree_entry_name(entry)); /* "README" */
/**
* You can also access tree entries by name if you know the name of the
* entry you're looking for.
*/
entry = git_tree_entry_byname(tree, "README");
git_tree_entry_name(entry); /* "README" */
/**
* Once you have the entry object, you can access the content or subtree
* (or commit, in the case of submodules) that it points to. You can also
* get the mode if you want.
*/
git_tree_entry_to_object(&obj, repo, entry); /* blob */
/**
* Remember to close the looked-up object and tree once you are done using it
*/
git_object_free(obj);
git_tree_free(tree);
}
/**
* #### Blob Parsing
*
* The last object type is the simplest and requires the least parsing
* help. Blobs are just file contents and can contain anything, there is
* no structure to it. The main advantage to using the [simple blob
* api][ba] is that when you're creating blobs you don't have to calculate
* the size of the content. There is also a helper for reading a file
* from disk and writing it to the db and getting the oid back so you
* don't have to do all those steps yourself.
*
* [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob
*/
static void blob_parsing(git_repository *repo)
{
git_blob *blob;
git_oid oid;
printf("\n*Blob Parsing*\n");
git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08");
git_blob_lookup(&blob, repo, &oid);
/**
* You can access a buffer with the raw contents of the blob directly.
* Note that this buffer may not be contain ASCII data for certain blobs
* (e.g. binary files): do not consider the buffer a NULL-terminated
* string, and use the `git_blob_rawsize` attribute to find out its exact
* size in bytes
* */
printf("Blob Size: %ld\n", (long)git_blob_rawsize(blob)); /* 8 */
git_blob_rawcontent(blob); /* "content" */
/**
* Free the blob after usage.
*/
git_blob_free(blob);
}
/**
* ### Revwalking
*
* The libgit2 [revision walking api][rw] provides methods to traverse the
* directed graph created by the parent pointers of the commit objects.
* Since all commits point back to the commit that came directly before
* them, you can walk this parentage as a graph and find all the commits
* that were ancestors of (reachable from) a given starting point. This
* can allow you to create `git log` type functionality.
*
* [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk
*/
static void revwalking(git_repository *repo)
{
const git_signature *cauth;
const char *cmsg;
int error;
git_revwalk *walk;
git_commit *wcommit;
git_oid oid;
printf("\n*Revwalking*\n");
git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644");
/**
* To use the revwalker, create a new walker, tell it how you want to sort
* the output and then push one or more starting points onto the walker.
* If you want to emulate the output of `git log` you would push the SHA
* of the commit that HEAD points to into the walker and then start
* traversing them. You can also 'hide' commits that you want to stop at
* or not see any of their ancestors. So if you want to emulate `git log
* branch1..branch2`, you would push the oid of `branch2` and hide the oid
* of `branch1`.
*/
git_revwalk_new(&walk, repo);
git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE);
git_revwalk_push(walk, &oid);
/**
* Now that we have the starting point pushed onto the walker, we start
* asking for ancestors. It will return them in the sorting order we asked
* for as commit oids. We can then lookup and parse the committed pointed
* at by the returned OID; note that this operation is specially fast
* since the raw contents of the commit object will be cached in memory
*/
while ((git_revwalk_next(&oid, walk)) == 0) {
error = git_commit_lookup(&wcommit, repo, &oid);
check_error(error, "looking up commit during revwalk");
cmsg = git_commit_message(wcommit);
cauth = git_commit_author(wcommit);
printf("%s (%s)\n", cmsg, cauth->email);
git_commit_free(wcommit);
}
/**
* Like the other objects, be sure to free the revwalker when you're done
* to prevent memory leaks. Also, make sure that the repository being
* walked it not deallocated while the walk is in progress, or it will
* result in undefined behavior
*/
git_revwalk_free(walk);
}
/**
* ### Index File Manipulation *
* The [index file API][gi] allows you to read, traverse, update and write
* the Git index file (sometimes thought of as the staging area).
*
* [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index
*/
static void index_walking(git_repository *repo)
{
git_index *index;
size_t i, ecount;
printf("\n*Index Walking*\n");
/**
* You can either open the index from the standard location in an open
* repository, as we're doing here, or you can open and manipulate any
* index file with `git_index_open_bare()`. The index for the repository
* will be located and loaded from disk.
*/
git_repository_index(&index, repo);
/**
* For each entry in the index, you can get a bunch of information
* including the SHA (oid), path and mode which map to the tree objects
* that are written out. It also has filesystem properties to help
* determine what to inspect for changes (ctime, mtime, dev, ino, uid,
* gid, file_size and flags) All these properties are exported publicly in
* the `git_index_entry` struct
*/
ecount = git_index_entrycount(index);
for (i = 0; i < ecount; ++i) {
const git_index_entry *e = git_index_get_byindex(index, i);
printf("path: %s\n", e->path);
printf("mtime: %d\n", (int)e->mtime.seconds);
printf("fs: %d\n", (int)e->file_size);
}
git_index_free(index);
}
/**
* ### References
*
* The [reference API][ref] allows you to list, resolve, create and update
* references such as branches, tags and remote references (everything in
* the .git/refs directory).
*
* [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference
*/
static void reference_listing(git_repository *repo)
{
git_strarray ref_list;
unsigned i;
printf("\n*Reference Listing*\n");
/**
* Here we will implement something like `git for-each-ref` simply listing
* out all available references and the object SHA they resolve to.
*
* Now that we have the list of reference names, we can lookup each ref
* one at a time and resolve them to the SHA, then print both values out.
*/
git_reference_list(&ref_list, repo);
for (i = 0; i < ref_list.count; ++i) {
git_reference *ref;
char oid_hex[GIT_OID_HEXSZ+1] = GIT_OID_HEX_ZERO;
const char *refname;
refname = ref_list.strings[i];
git_reference_lookup(&ref, repo, refname);
switch (git_reference_type(ref)) {
case GIT_REFERENCE_DIRECT:
git_oid_fmt(oid_hex, git_reference_target(ref));
printf("%s [%s]\n", refname, oid_hex);
break;
case GIT_REFERENCE_SYMBOLIC:
printf("%s => %s\n", refname, git_reference_symbolic_target(ref));
break;
default:
fprintf(stderr, "Unexpected reference type\n");
exit(1);
}
git_reference_free(ref);
}
git_strarray_free(&ref_list);
}
/**
* ### Config Files
*
* The [config API][config] allows you to list and updatee config values
* in any of the accessible config file locations (system, global, local).
*
* [config]: http://libgit2.github.com/libgit2/#HEAD/group/config
*/
static void config_files(const char *repo_path, git_repository* repo)
{
const char *email;
char config_path[256];
int32_t autocorrect;
git_config *cfg;
git_config *snap_cfg;
int error_code;
printf("\n*Config Listing*\n");
/**
* Open a config object so we can read global values from it.
*/
sprintf(config_path, "%s/config", repo_path);
check_error(git_config_open_ondisk(&cfg, config_path), "opening config");
if (git_config_get_int32(&autocorrect, cfg, "help.autocorrect") == 0)
printf("Autocorrect: %d\n", autocorrect);
check_error(git_repository_config_snapshot(&snap_cfg, repo), "config snapshot");
git_config_get_string(&email, snap_cfg, "user.email");
printf("Email: %s\n", email);
error_code = git_config_get_int32(&autocorrect, cfg, "help.autocorrect");
switch (error_code)
{
case 0:
printf("Autocorrect: %d\n", autocorrect);
break;
case GIT_ENOTFOUND:
printf("Autocorrect: Undefined\n");
break;
default:
check_error(error_code, "get_int32 failed");
}
git_config_free(cfg);
check_error(git_repository_config_snapshot(&snap_cfg, repo), "config snapshot");
error_code = git_config_get_string(&email, snap_cfg, "user.email");
switch (error_code)
{
case 0:
printf("Email: %s\n", email);
break;
case GIT_ENOTFOUND:
printf("Email: Undefined\n");
break;
default:
check_error(error_code, "get_string failed");
}
git_config_free(snap_cfg);
}
+71
View File
@@ -0,0 +1,71 @@
#include "common.h"
/*
* This could be run in the main loop whilst the application waits for
* the indexing to finish in a worker thread
*/
static int index_cb(const git_indexer_progress *stats, void *data)
{
(void)data;
printf("\rProcessing %u of %u", stats->indexed_objects, stats->total_objects);
return 0;
}
int lg2_index_pack(git_repository *repo, int argc, char **argv)
{
git_indexer *idx;
git_indexer_progress stats = {0, 0};
int error;
char hash[GIT_OID_HEXSZ + 1] = {0};
int fd;
ssize_t read_bytes;
char buf[512];
(void)repo;
if (argc < 2) {
fprintf(stderr, "usage: %s index-pack <packfile>\n", argv[-1]);
return EXIT_FAILURE;
}
if (git_indexer_new(&idx, ".", 0, NULL, NULL) < 0) {
puts("bad idx");
return -1;
}
if ((fd = open(argv[1], 0)) < 0) {
perror("open");
return -1;
}
do {
read_bytes = read(fd, buf, sizeof(buf));
if (read_bytes < 0)
break;
if ((error = git_indexer_append(idx, buf, read_bytes, &stats)) < 0)
goto cleanup;
index_cb(&stats, NULL);
} while (read_bytes > 0);
if (read_bytes < 0) {
error = -1;
perror("failed reading");
goto cleanup;
}
if ((error = git_indexer_commit(idx, &stats)) < 0)
goto cleanup;
printf("\rIndexing %u of %u\n", stats.indexed_objects, stats.total_objects);
git_oid_fmt(hash, git_indexer_hash(idx));
puts(hash);
cleanup:
close(fd);
git_indexer_free(idx);
return error;
}
+248
View File
@@ -0,0 +1,248 @@
/*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#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 init_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 init_opts *o, int argc, char *argv[]);
int lg2_init(git_repository *repo, int argc, char *argv[])
{
struct init_opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 };
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);
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=<dir>]\n"
" [--shared[=perms]] [--initial-commit]\n"
" [--separate-git-dir] <directory>\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 init_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", "");
}
+122
View File
@@ -0,0 +1,122 @@
#include "common.h"
/* This part is not strictly libgit2-dependent, but you can use this
* as a starting point for a git-like tool */
typedef int (*git_command_fn)(git_repository *, int , char **);
struct {
char *name;
git_command_fn fn;
char requires_repo;
} commands[] = {
{ "add", lg2_add, 1 },
{ "blame", lg2_blame, 1 },
{ "cat-file", lg2_cat_file, 1 },
{ "checkout", lg2_checkout, 1 },
{ "clone", lg2_clone, 0 },
{ "config", lg2_config, 1 },
{ "describe", lg2_describe, 1 },
{ "diff", lg2_diff, 1 },
{ "fetch", lg2_fetch, 1 },
{ "for-each-ref", lg2_for_each_ref, 1 },
{ "general", lg2_general, 0 },
{ "index-pack", lg2_index_pack, 1 },
{ "init", lg2_init, 0 },
{ "log", lg2_log, 1 },
{ "ls-files", lg2_ls_files, 1 },
{ "ls-remote", lg2_ls_remote, 1 },
{ "merge", lg2_merge, 1 },
{ "remote", lg2_remote, 1 },
{ "rev-list", lg2_rev_list, 1 },
{ "rev-parse", lg2_rev_parse, 1 },
{ "show-index", lg2_show_index, 0 },
{ "stash", lg2_stash, 1 },
{ "status", lg2_status, 1 },
{ "tag", lg2_tag, 1 },
};
static int run_command(git_command_fn fn, git_repository *repo, struct args_info args)
{
int error;
/* Run the command. If something goes wrong, print the error message to stderr */
error = fn(repo, args.argc - args.pos, &args.argv[args.pos]);
if (error < 0) {
if (git_error_last() == NULL)
fprintf(stderr, "Error without message");
else
fprintf(stderr, "Bad news:\n %s\n", git_error_last()->message);
}
return !!error;
}
static int usage(const char *prog)
{
size_t i;
fprintf(stderr, "usage: %s <cmd>...\n\nAvailable commands:\n\n", prog);
for (i = 0; i < ARRAY_SIZE(commands); i++)
fprintf(stderr, "\t%s\n", commands[i].name);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
git_repository *repo = NULL;
const char *git_dir = NULL;
int return_code = 1;
size_t i;
if (argc < 2)
usage(argv[0]);
git_libgit2_init();
for (args.pos = 1; args.pos < args.argc; ++args.pos) {
char *a = args.argv[args.pos];
if (a[0] != '-') {
/* non-arg */
break;
} else if (optional_str_arg(&git_dir, &args, "--git-dir", ".git")) {
continue;
} else if (match_arg_separator(&args)) {
break;
}
}
if (args.pos == args.argc)
usage(argv[0]);
if (!git_dir)
git_dir = ".";
for (i = 0; i < ARRAY_SIZE(commands); ++i) {
if (strcmp(args.argv[args.pos], commands[i].name))
continue;
/*
* Before running the actual command, create an instance
* of the local repository and pass it to the function.
* */
if (commands[i].requires_repo) {
check_lg2(git_repository_open_ext(&repo, git_dir, 0, NULL),
"Unable to open repository '%s'", git_dir);
}
return_code = run_command(commands[i].fn, repo, args);
goto shutdown;
}
fprintf(stderr, "Command not found: %s\n", argv[1]);
shutdown:
git_repository_free(repo);
git_libgit2_shutdown();
return return_code;
}
+474
View File
@@ -0,0 +1,474 @@
/*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#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 show_log_size;
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, struct log_options *opts);
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 lg2_log(git_repository *repo, 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;
/** Parse arguments and set up revwalker. */
last_arg = parse_options(&s, &opt, argc, argv);
s.repo = repo;
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, &opt);
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);
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;
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_OBJECT_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, struct log_options *opts)
{
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 (opts->show_log_size) {
printf("log size %d\n", (int)strlen(git_commit_message(commit)));
}
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 [<options>]\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 (!match_arg_separator(&args)) {
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 if (!strcmp(a, "--log-size"))
opt->show_log_size = 1;
else
usage("Unsupported argument", a);
}
return args.pos;
}
+131
View File
@@ -0,0 +1,131 @@
/*
* libgit2 "ls-files" example - shows how to view all files currently in the index
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates the libgit2 index APIs to roughly
* simulate the output of `git ls-files`.
* `git ls-files` has many options and this currently does not show them.
*
* `git ls-files` base command shows all paths in the index at that time.
* This includes staged and committed files, but unstaged files will not display.
*
* This currently supports the default behavior and the `--error-unmatch` option.
*/
struct ls_options {
int error_unmatch;
char *files[1024];
size_t file_count;
};
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: ls-files [--error-unmatch] [--] [<file>...]\n");
exit(1);
}
static int parse_options(struct ls_options *opts, int argc, char *argv[])
{
int parsing_files = 0;
int i;
memset(opts, 0, sizeof(struct ls_options));
if (argc < 2)
return 0;
for (i = 1; i < argc; ++i) {
char *a = argv[i];
/* if it doesn't start with a '-' or is after the '--' then it is a file */
if (a[0] != '-' || parsing_files) {
parsing_files = 1;
/* watch for overflows (just in case) */
if (opts->file_count == 1024) {
fprintf(stderr, "ls-files can only support 1024 files at this time.\n");
return -1;
}
opts->files[opts->file_count++] = a;
} else if (!strcmp(a, "--")) {
parsing_files = 1;
} else if (!strcmp(a, "--error-unmatch")) {
opts->error_unmatch = 1;
} else {
usage("Unsupported argument", a);
return -1;
}
}
return 0;
}
static int print_paths(struct ls_options *opts, git_index *index)
{
size_t i;
const git_index_entry *entry;
/* if there are no files explicitly listed by the user print all entries in the index */
if (opts->file_count == 0) {
size_t entry_count = git_index_entrycount(index);
for (i = 0; i < entry_count; i++) {
entry = git_index_get_byindex(index, i);
puts(entry->path);
}
return 0;
}
/* loop through the files found in the args and print them if they exist */
for (i = 0; i < opts->file_count; ++i) {
const char *path = opts->files[i];
if ((entry = git_index_get_bypath(index, path, GIT_INDEX_STAGE_NORMAL)) != NULL) {
puts(path);
} else if (opts->error_unmatch) {
fprintf(stderr, "error: pathspec '%s' did not match any file(s) known to git.\n", path);
fprintf(stderr, "Did you forget to 'git add'?\n");
return -1;
}
}
return 0;
}
int lg2_ls_files(git_repository *repo, int argc, char *argv[])
{
git_index *index = NULL;
struct ls_options opts;
int error;
if ((error = parse_options(&opts, argc, argv)) < 0)
return error;
if ((error = git_repository_index(&index, repo)) < 0)
goto cleanup;
error = print_paths(&opts, index);
cleanup:
git_index_free(index);
return error;
}
+60
View File
@@ -0,0 +1,60 @@
#include "common.h"
static int use_remote(git_repository *repo, char *name)
{
git_remote *remote = NULL;
int error;
const git_remote_head **refs;
size_t refs_len, i;
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
/* Find the remote by name */
error = git_remote_lookup(&remote, repo, name);
if (error < 0) {
error = git_remote_create_anonymous(&remote, repo, name);
if (error < 0)
goto cleanup;
}
/**
* Connect to the remote and call the printing function for
* each of the remote references.
*/
callbacks.credentials = cred_acquire_cb;
error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL, NULL);
if (error < 0)
goto cleanup;
/**
* Get the list of references on the remote and print out
* their name next to what they point to.
*/
if (git_remote_ls(&refs, &refs_len, remote) < 0)
goto cleanup;
for (i = 0; i < refs_len; i++) {
char oid[GIT_OID_HEXSZ + 1] = {0};
git_oid_fmt(oid, &refs[i]->oid);
printf("%s\t%s\n", oid, refs[i]->name);
}
cleanup:
git_remote_free(remote);
return error;
}
/** Entry point for this command */
int lg2_ls_remote(git_repository *repo, int argc, char **argv)
{
int error;
if (argc < 2) {
fprintf(stderr, "usage: %s ls-remote <remote>\n", argv[-1]);
return EXIT_FAILURE;
}
error = use_remote(repo, argv[1]);
return error;
}
+361
View File
@@ -0,0 +1,361 @@
/*
* libgit2 "merge" example - shows how to perform merges
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/** The following example demonstrates how to do merges with libgit2.
*
* It will merge whatever commit-ish you pass in into the current branch.
*
* Recognized options are :
* --no-commit: don't actually commit the merge.
*
*/
struct merge_options {
const char **heads;
size_t heads_count;
git_annotated_commit **annotated;
size_t annotated_count;
int no_commit : 1;
};
static void print_usage(void)
{
fprintf(stderr, "usage: merge [--no-commit] <commit...>\n");
exit(1);
}
static void merge_options_init(struct merge_options *opts)
{
memset(opts, 0, sizeof(*opts));
opts->heads = NULL;
opts->heads_count = 0;
opts->annotated = NULL;
opts->annotated_count = 0;
}
static void opts_add_refish(struct merge_options *opts, const char *refish)
{
size_t sz;
assert(opts != NULL);
sz = ++opts->heads_count * sizeof(opts->heads[0]);
opts->heads = xrealloc((void *) opts->heads, sz);
opts->heads[opts->heads_count - 1] = refish;
}
static void parse_options(const char **repo_path, struct merge_options *opts, int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
if (argc <= 1)
print_usage();
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *curr = argv[args.pos];
if (curr[0] != '-') {
opts_add_refish(opts, curr);
} else if (!strcmp(curr, "--no-commit")) {
opts->no_commit = 1;
} else if (match_str_arg(repo_path, &args, "--git-dir")) {
continue;
} else {
print_usage();
}
}
}
static int resolve_heads(git_repository *repo, struct merge_options *opts)
{
git_annotated_commit **annotated = calloc(opts->heads_count, sizeof(git_annotated_commit *));
size_t annotated_count = 0, i;
int err = 0;
for (i = 0; i < opts->heads_count; i++) {
err = resolve_refish(&annotated[annotated_count++], repo, opts->heads[i]);
if (err != 0) {
fprintf(stderr, "failed to resolve refish %s: %s\n", opts->heads[i], git_error_last()->message);
annotated_count--;
continue;
}
}
if (annotated_count != opts->heads_count) {
fprintf(stderr, "unable to parse some refish\n");
free(annotated);
return -1;
}
opts->annotated = annotated;
opts->annotated_count = annotated_count;
return 0;
}
static int perform_fastforward(git_repository *repo, const git_oid *target_oid, int is_unborn)
{
git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
git_reference *target_ref;
git_reference *new_target_ref;
git_object *target = NULL;
int err = 0;
if (is_unborn) {
const char *symbolic_ref;
git_reference *head_ref;
/* HEAD reference is unborn, lookup manually so we don't try to resolve it */
err = git_reference_lookup(&head_ref, repo, "HEAD");
if (err != 0) {
fprintf(stderr, "failed to lookup HEAD ref\n");
return -1;
}
/* Grab the reference HEAD should be pointing to */
symbolic_ref = git_reference_symbolic_target(head_ref);
/* Create our master reference on the target OID */
err = git_reference_create(&target_ref, repo, symbolic_ref, target_oid, 0, NULL);
if (err != 0) {
fprintf(stderr, "failed to create master reference\n");
return -1;
}
git_reference_free(head_ref);
} else {
/* HEAD exists, just lookup and resolve */
err = git_repository_head(&target_ref, repo);
if (err != 0) {
fprintf(stderr, "failed to get HEAD reference\n");
return -1;
}
}
/* Lookup the target object */
err = git_object_lookup(&target, repo, target_oid, GIT_OBJECT_COMMIT);
if (err != 0) {
fprintf(stderr, "failed to lookup OID %s\n", git_oid_tostr_s(target_oid));
return -1;
}
/* Checkout the result so the workdir is in the expected state */
ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
err = git_checkout_tree(repo, target, &ff_checkout_options);
if (err != 0) {
fprintf(stderr, "failed to checkout HEAD reference\n");
return -1;
}
/* Move the target reference to the target OID */
err = git_reference_set_target(&new_target_ref, target_ref, target_oid, NULL);
if (err != 0) {
fprintf(stderr, "failed to move HEAD reference\n");
return -1;
}
git_reference_free(target_ref);
git_reference_free(new_target_ref);
git_object_free(target);
return 0;
}
static void output_conflicts(git_index *index)
{
git_index_conflict_iterator *conflicts;
const git_index_entry *ancestor;
const git_index_entry *our;
const git_index_entry *their;
int err = 0;
check_lg2(git_index_conflict_iterator_new(&conflicts, index), "failed to create conflict iterator", NULL);
while ((err = git_index_conflict_next(&ancestor, &our, &their, conflicts)) == 0) {
fprintf(stderr, "conflict: a:%s o:%s t:%s\n",
ancestor ? ancestor->path : "NULL",
our->path ? our->path : "NULL",
their->path ? their->path : "NULL");
}
if (err != GIT_ITEROVER) {
fprintf(stderr, "error iterating conflicts\n");
}
git_index_conflict_iterator_free(conflicts);
}
static int create_merge_commit(git_repository *repo, git_index *index, struct merge_options *opts)
{
git_oid tree_oid, commit_oid;
git_tree *tree;
git_signature *sign;
git_reference *merge_ref = NULL;
git_annotated_commit *merge_commit;
git_reference *head_ref;
git_commit **parents = calloc(opts->annotated_count + 1, sizeof(git_commit *));
const char *msg_target = NULL;
size_t msglen = 0;
char *msg;
size_t i;
int err;
/* Grab our needed references */
check_lg2(git_repository_head(&head_ref, repo), "failed to get repo HEAD", NULL);
if (resolve_refish(&merge_commit, repo, opts->heads[0])) {
fprintf(stderr, "failed to resolve refish %s", opts->heads[0]);
free(parents);
return -1;
}
/* Maybe that's a ref, so DWIM it */
err = git_reference_dwim(&merge_ref, repo, opts->heads[0]);
check_lg2(err, "failed to DWIM reference", git_error_last()->message);
/* Grab a signature */
check_lg2(git_signature_now(&sign, "Me", "me@example.com"), "failed to create signature", NULL);
#define MERGE_COMMIT_MSG "Merge %s '%s'"
/* Prepare a standard merge commit message */
if (merge_ref != NULL) {
check_lg2(git_branch_name(&msg_target, merge_ref), "failed to get branch name of merged ref", NULL);
} else {
msg_target = git_oid_tostr_s(git_annotated_commit_id(merge_commit));
}
msglen = snprintf(NULL, 0, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target);
if (msglen > 0) msglen++;
msg = malloc(msglen);
err = snprintf(msg, msglen, MERGE_COMMIT_MSG, (merge_ref ? "branch" : "commit"), msg_target);
/* This is only to silence the compiler */
if (err < 0) goto cleanup;
/* Setup our parent commits */
err = git_reference_peel((git_object **)&parents[0], head_ref, GIT_OBJECT_COMMIT);
check_lg2(err, "failed to peel head reference", NULL);
for (i = 0; i < opts->annotated_count; i++) {
git_commit_lookup(&parents[i + 1], repo, git_annotated_commit_id(opts->annotated[i]));
}
/* Prepare our commit tree */
check_lg2(git_index_write_tree(&tree_oid, index), "failed to write merged tree", NULL);
check_lg2(git_tree_lookup(&tree, repo, &tree_oid), "failed to lookup tree", NULL);
/* Commit time ! */
err = git_commit_create(&commit_oid,
repo, git_reference_name(head_ref),
sign, sign,
NULL, msg,
tree,
opts->annotated_count + 1, (const git_commit **)parents);
check_lg2(err, "failed to create commit", NULL);
/* We're done merging, cleanup the repository state */
git_repository_state_cleanup(repo);
cleanup:
free(parents);
return err;
}
int lg2_merge(git_repository *repo, int argc, char **argv)
{
struct merge_options opts;
git_index *index;
git_repository_state_t state;
git_merge_analysis_t analysis;
git_merge_preference_t preference;
const char *path = ".";
int err = 0;
merge_options_init(&opts);
parse_options(&path, &opts, argc, argv);
state = git_repository_state(repo);
if (state != GIT_REPOSITORY_STATE_NONE) {
fprintf(stderr, "repository is in unexpected state %d\n", state);
goto cleanup;
}
err = resolve_heads(repo, &opts);
if (err != 0)
goto cleanup;
err = git_merge_analysis(&analysis, &preference,
repo,
(const git_annotated_commit **)opts.annotated,
opts.annotated_count);
check_lg2(err, "merge analysis failed", NULL);
if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) {
printf("Already up-to-date\n");
return 0;
} else if (analysis & GIT_MERGE_ANALYSIS_UNBORN ||
(analysis & GIT_MERGE_ANALYSIS_FASTFORWARD &&
!(preference & GIT_MERGE_PREFERENCE_NO_FASTFORWARD))) {
const git_oid *target_oid;
if (analysis & GIT_MERGE_ANALYSIS_UNBORN) {
printf("Unborn\n");
} else {
printf("Fast-forward\n");
}
/* Since this is a fast-forward, there can be only one merge head */
target_oid = git_annotated_commit_id(opts.annotated[0]);
assert(opts.annotated_count == 1);
return perform_fastforward(repo, target_oid, (analysis & GIT_MERGE_ANALYSIS_UNBORN));
} else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) {
git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
merge_opts.flags = 0;
merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3;
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE|GIT_CHECKOUT_ALLOW_CONFLICTS;
if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) {
printf("Fast-forward is preferred, but only a merge is possible\n");
return -1;
}
err = git_merge(repo,
(const git_annotated_commit **)opts.annotated, opts.annotated_count,
&merge_opts, &checkout_opts);
check_lg2(err, "merge failed", NULL);
}
/* If we get here, we actually performed the merge above */
check_lg2(git_repository_index(&index, repo), "failed to get repository index", NULL);
if (git_index_has_conflicts(index)) {
/* Handle conflicts */
output_conflicts(index);
} else if (!opts.no_commit) {
create_merge_commit(repo, index, &opts);
printf("Merge made\n");
}
cleanup:
free((char **)opts.heads);
free(opts.annotated);
return 0;
}
+256
View File
@@ -0,0 +1,256 @@
/*
* libgit2 "remote" example - shows how to modify remotes for a 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This is a sample program that is similar to "git remote". See the
* documentation for that (try "git help remote") to understand what this
* program is emulating.
*
* This demonstrates using the libgit2 APIs to modify remotes of a repository.
*/
enum subcmd {
subcmd_add,
subcmd_remove,
subcmd_rename,
subcmd_seturl,
subcmd_show,
};
struct remote_opts {
enum subcmd cmd;
/* for command-specific args */
int argc;
char **argv;
};
static int cmd_add(git_repository *repo, struct remote_opts *o);
static int cmd_remove(git_repository *repo, struct remote_opts *o);
static int cmd_rename(git_repository *repo, struct remote_opts *o);
static int cmd_seturl(git_repository *repo, struct remote_opts *o);
static int cmd_show(git_repository *repo, struct remote_opts *o);
static void parse_subcmd(
struct remote_opts *opt, int argc, char **argv);
static void usage(const char *msg, const char *arg);
int lg2_remote(git_repository *repo, int argc, char *argv[])
{
int retval = 0;
struct remote_opts opt = {0};
parse_subcmd(&opt, argc, argv);
switch (opt.cmd)
{
case subcmd_add:
retval = cmd_add(repo, &opt);
break;
case subcmd_remove:
retval = cmd_remove(repo, &opt);
break;
case subcmd_rename:
retval = cmd_rename(repo, &opt);
break;
case subcmd_seturl:
retval = cmd_seturl(repo, &opt);
break;
case subcmd_show:
retval = cmd_show(repo, &opt);
break;
}
return retval;
}
static int cmd_add(git_repository *repo, struct remote_opts *o)
{
char *name, *url;
git_remote *remote = {0};
if (o->argc != 2)
usage("you need to specify a name and URL", NULL);
name = o->argv[0];
url = o->argv[1];
check_lg2(git_remote_create(&remote, repo, name, url),
"could not create remote", NULL);
return 0;
}
static int cmd_remove(git_repository *repo, struct remote_opts *o)
{
char *name;
if (o->argc != 1)
usage("you need to specify a name", NULL);
name = o->argv[0];
check_lg2(git_remote_delete(repo, name),
"could not delete remote", name);
return 0;
}
static int cmd_rename(git_repository *repo, struct remote_opts *o)
{
int i, retval;
char *old, *new;
git_strarray problems = {0};
if (o->argc != 2)
usage("you need to specify old and new remote name", NULL);
old = o->argv[0];
new = o->argv[1];
retval = git_remote_rename(&problems, repo, old, new);
if (!retval)
return 0;
for (i = 0; i < (int) problems.count; i++) {
puts(problems.strings[0]);
}
git_strarray_free(&problems);
return retval;
}
static int cmd_seturl(git_repository *repo, struct remote_opts *o)
{
int i, retval, push = 0;
char *name = NULL, *url = NULL;
for (i = 0; i < o->argc; i++) {
char *arg = o->argv[i];
if (!strcmp(arg, "--push")) {
push = 1;
} else if (arg[0] != '-' && name == NULL) {
name = arg;
} else if (arg[0] != '-' && url == NULL) {
url = arg;
} else {
usage("invalid argument to set-url", arg);
}
}
if (name == NULL || url == NULL)
usage("you need to specify remote and the new URL", NULL);
if (push)
retval = git_remote_set_pushurl(repo, name, url);
else
retval = git_remote_set_url(repo, name, url);
check_lg2(retval, "could not set URL", url);
return 0;
}
static int cmd_show(git_repository *repo, struct remote_opts *o)
{
int i;
const char *arg, *name, *fetch, *push;
int verbose = 0;
git_strarray remotes = {0};
git_remote *remote = {0};
for (i = 0; i < o->argc; i++) {
arg = o->argv[i];
if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) {
verbose = 1;
}
}
check_lg2(git_remote_list(&remotes, repo),
"could not retrieve remotes", NULL);
for (i = 0; i < (int) remotes.count; i++) {
name = remotes.strings[i];
if (!verbose) {
puts(name);
continue;
}
check_lg2(git_remote_lookup(&remote, repo, name),
"could not look up remote", name);
fetch = git_remote_url(remote);
if (fetch)
printf("%s\t%s (fetch)\n", name, fetch);
push = git_remote_pushurl(remote);
/* use fetch URL if no distinct push URL has been set */
push = push ? push : fetch;
if (push)
printf("%s\t%s (push)\n", name, push);
git_remote_free(remote);
}
git_strarray_free(&remotes);
return 0;
}
static void parse_subcmd(
struct remote_opts *opt, int argc, char **argv)
{
char *arg = argv[1];
enum subcmd cmd = 0;
if (argc < 2)
usage("no command specified", NULL);
if (!strcmp(arg, "add")) {
cmd = subcmd_add;
} else if (!strcmp(arg, "remove")) {
cmd = subcmd_remove;
} else if (!strcmp(arg, "rename")) {
cmd = subcmd_rename;
} else if (!strcmp(arg, "set-url")) {
cmd = subcmd_seturl;
} else if (!strcmp(arg, "show")) {
cmd = subcmd_show;
} else {
usage("command is not valid", arg);
}
opt->cmd = cmd;
opt->argc = argc - 2; /* executable and subcommand are removed */
opt->argv = argv + 2;
}
static void usage(const char *msg, const char *arg)
{
fputs("usage: remote add <name> <url>\n", stderr);
fputs(" remote remove <name>\n", stderr);
fputs(" remote rename <old> <new>\n", stderr);
fputs(" remote set-url [--push] <name> <newurl>\n", stderr);
fputs(" remote show [-v|--verbose]\n", stderr);
if (msg && !arg)
fprintf(stderr, "\n%s\n", msg);
else if (msg && arg)
fprintf(stderr, "\n%s: %s\n", msg, arg);
exit(1);
}
+152
View File
@@ -0,0 +1,152 @@
/*
* libgit2 "rev-list" example - shows how to transform a rev-spec into a list
* of commit ids
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
#include <assert.h>
static int revwalk_parse_options(git_sort_t *sort, struct args_info *args);
static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args);
int lg2_rev_list(git_repository *repo, int argc, char **argv)
{
struct args_info args = ARGS_INFO_INIT;
git_revwalk *walk;
git_oid oid;
git_sort_t sort;
char buf[GIT_OID_HEXSZ+1];
check_lg2(revwalk_parse_options(&sort, &args), "parsing options", NULL);
check_lg2(git_revwalk_new(&walk, repo), "allocating revwalk", NULL);
git_revwalk_sorting(walk, sort);
check_lg2(revwalk_parse_revs(repo, walk, &args), "parsing revs", NULL);
while (!git_revwalk_next(&oid, walk)) {
git_oid_fmt(buf, &oid);
buf[GIT_OID_HEXSZ] = '\0';
printf("%s\n", buf);
}
git_revwalk_free(walk);
return 0;
}
static int push_commit(git_revwalk *walk, const git_oid *oid, int hide)
{
if (hide)
return git_revwalk_hide(walk, oid);
else
return git_revwalk_push(walk, oid);
}
static int push_spec(git_repository *repo, git_revwalk *walk, const char *spec, int hide)
{
int error;
git_object *obj;
if ((error = git_revparse_single(&obj, repo, spec)) < 0)
return error;
error = push_commit(walk, git_object_id(obj), hide);
git_object_free(obj);
return error;
}
static int push_range(git_repository *repo, git_revwalk *walk, const char *range, int hide)
{
git_revspec revspec;
int error = 0;
if ((error = git_revparse(&revspec, repo, range)))
return error;
if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {
/* TODO: support "<commit>...<commit>" */
return GIT_EINVALIDSPEC;
}
if ((error = push_commit(walk, git_object_id(revspec.from), !hide)))
goto out;
error = push_commit(walk, git_object_id(revspec.to), hide);
out:
git_object_free(revspec.from);
git_object_free(revspec.to);
return error;
}
static void print_usage(void)
{
fprintf(stderr, "rev-list [--git-dir=dir] [--topo-order|--date-order] [--reverse] <revspec>\n");
exit(-1);
}
static int revwalk_parse_options(git_sort_t *sort, struct args_info *args)
{
assert(sort && args);
*sort = GIT_SORT_NONE;
if (args->argc < 1)
print_usage();
for (args->pos = 1; args->pos < args->argc; ++args->pos) {
const char *curr = args->argv[args->pos];
if (!strcmp(curr, "--topo-order")) {
*sort |= GIT_SORT_TOPOLOGICAL;
} else if (!strcmp(curr, "--date-order")) {
*sort |= GIT_SORT_TIME;
} else if (!strcmp(curr, "--reverse")) {
*sort |= (*sort & ~GIT_SORT_REVERSE) ^ GIT_SORT_REVERSE;
} else {
break;
}
}
return 0;
}
static int revwalk_parse_revs(git_repository *repo, git_revwalk *walk, struct args_info *args)
{
int hide, error;
git_oid oid;
hide = 0;
for (; args->pos < args->argc; ++args->pos) {
const char *curr = args->argv[args->pos];
if (!strcmp(curr, "--not")) {
hide = !hide;
} else if (curr[0] == '^') {
if ((error = push_spec(repo, walk, curr + 1, !hide)))
return error;
} else if (strstr(curr, "..")) {
if ((error = push_range(repo, walk, curr, hide)))
return error;
} else {
if (push_spec(repo, walk, curr, hide) == 0)
continue;
if ((error = git_oid_fromstr(&oid, curr)))
return error;
if ((error = push_commit(walk, &oid, hide)))
return error;
}
}
return 0;
}
+102
View File
@@ -0,0 +1,102 @@
/*
* libgit2 "rev-parse" example - shows how to parse revspecs
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/** Forward declarations for helpers. */
struct parse_state {
const char *repodir;
const char *spec;
int not;
};
static void parse_opts(struct parse_state *ps, int argc, char *argv[]);
static int parse_revision(git_repository *repo, struct parse_state *ps);
int lg2_rev_parse(git_repository *repo, int argc, char *argv[])
{
struct parse_state ps = {0};
parse_opts(&ps, argc, argv);
check_lg2(parse_revision(repo, &ps), "Parsing", NULL);
return 0;
}
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: rev-parse [ --option ] <args>...\n");
exit(1);
}
static void parse_opts(struct parse_state *ps, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos=1; args.pos < argc; ++args.pos) {
const char *a = argv[args.pos];
if (a[0] != '-') {
if (ps->spec)
usage("Too many specs", a);
ps->spec = a;
} else if (!strcmp(a, "--not"))
ps->not = !ps->not;
else if (!match_str_arg(&ps->repodir, &args, "--git-dir"))
usage("Cannot handle argument", a);
}
}
static int parse_revision(git_repository *repo, struct parse_state *ps)
{
git_revspec rs;
char str[GIT_OID_HEXSZ + 1];
check_lg2(git_revparse(&rs, repo, ps->spec), "Could not parse", ps->spec);
if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("%s\n", str);
git_object_free(rs.from);
}
else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.to));
printf("%s\n", str);
git_object_free(rs.to);
if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
git_oid base;
check_lg2(git_merge_base(&base, repo,
git_object_id(rs.from), git_object_id(rs.to)),
"Could not find merge base", ps->spec);
git_oid_tostr(str, sizeof(str), &base);
printf("%s\n", str);
}
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("^%s\n", str);
git_object_free(rs.from);
}
else {
fatal("Invalid results from git_revparse", ps->spec);
}
return 0;
}
+66
View File
@@ -0,0 +1,66 @@
/*
* libgit2 "showindex" example - shows how to extract data from the index
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
int lg2_show_index(git_repository *repo, int argc, char **argv)
{
git_index *index;
size_t i, ecount;
char *dir = ".";
size_t dirlen;
char out[GIT_OID_HEXSZ+1];
out[GIT_OID_HEXSZ] = '\0';
if (argc > 2)
fatal("usage: showindex [<repo-dir>]", NULL);
if (argc > 1)
dir = argv[1];
dirlen = strlen(dir);
if (dirlen > 5 && strcmp(dir + dirlen - 5, "index") == 0) {
check_lg2(git_index_open(&index, dir), "could not open index", dir);
} else {
check_lg2(git_repository_open_ext(&repo, dir, 0, NULL), "could not open repository", dir);
check_lg2(git_repository_index(&index, repo), "could not open repository index", NULL);
git_repository_free(repo);
}
git_index_read(index, 0);
ecount = git_index_entrycount(index);
if (!ecount)
printf("Empty index\n");
for (i = 0; i < ecount; ++i) {
const git_index_entry *e = git_index_get_byindex(index, i);
git_oid_fmt(out, &e->id);
printf("File Path: %s\n", e->path);
printf(" Stage: %d\n", git_index_entry_stage(e));
printf(" Blob SHA: %s\n", out);
printf("File Mode: %07o\n", e->mode);
printf("File Size: %d bytes\n", (int)e->file_size);
printf("Dev/Inode: %d/%d\n", (int)e->dev, (int)e->ino);
printf(" UID/GID: %d/%d\n", (int)e->uid, (int)e->gid);
printf(" ctime: %d\n", (int)e->ctime.seconds);
printf(" mtime: %d\n", (int)e->mtime.seconds);
printf("\n");
}
git_index_free(index);
return 0;
}
+157
View File
@@ -0,0 +1,157 @@
/*
* libgit2 "stash" example - shows how to use the stash API
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include <stdarg.h>
#include "common.h"
enum subcmd {
SUBCMD_APPLY,
SUBCMD_LIST,
SUBCMD_POP,
SUBCMD_PUSH
};
struct opts {
enum subcmd cmd;
int argc;
char **argv;
};
static void usage(const char *fmt, ...)
{
va_list ap;
fputs("usage: git stash list\n", stderr);
fputs(" or: git stash ( pop | apply )\n", stderr);
fputs(" or: git stash [push]\n", stderr);
fputs("\n", stderr);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(1);
}
static void parse_subcommand(struct opts *opts, int argc, char *argv[])
{
char *arg = (argc < 2) ? "push" : argv[1];
enum subcmd cmd;
if (!strcmp(arg, "apply")) {
cmd = SUBCMD_APPLY;
} else if (!strcmp(arg, "list")) {
cmd = SUBCMD_LIST;
} else if (!strcmp(arg, "pop")) {
cmd = SUBCMD_POP;
} else if (!strcmp(arg, "push")) {
cmd = SUBCMD_PUSH;
} else {
usage("invalid command %s", arg);
return;
}
opts->cmd = cmd;
opts->argc = (argc < 2) ? argc - 1 : argc - 2;
opts->argv = argv;
}
static int cmd_apply(git_repository *repo, struct opts *opts)
{
if (opts->argc)
usage("apply does not accept any parameters");
check_lg2(git_stash_apply(repo, 0, NULL),
"Unable to apply stash", NULL);
return 0;
}
static int list_stash_cb(size_t index, const char *message,
const git_oid *stash_id, void *payload)
{
UNUSED(stash_id);
UNUSED(payload);
printf("stash@{%"PRIuZ"}: %s\n", index, message);
return 0;
}
static int cmd_list(git_repository *repo, struct opts *opts)
{
if (opts->argc)
usage("list does not accept any parameters");
check_lg2(git_stash_foreach(repo, list_stash_cb, NULL),
"Unable to list stashes", NULL);
return 0;
}
static int cmd_push(git_repository *repo, struct opts *opts)
{
git_signature *signature;
git_commit *stash;
git_oid stashid;
if (opts->argc)
usage("push does not accept any parameters");
check_lg2(git_signature_default(&signature, repo),
"Unable to get signature", NULL);
check_lg2(git_stash_save(&stashid, repo, signature, NULL, GIT_STASH_DEFAULT),
"Unable to save stash", NULL);
check_lg2(git_commit_lookup(&stash, repo, &stashid),
"Unable to lookup stash commit", NULL);
printf("Saved working directory %s\n", git_commit_summary(stash));
git_signature_free(signature);
git_commit_free(stash);
return 0;
}
static int cmd_pop(git_repository *repo, struct opts *opts)
{
if (opts->argc)
usage("pop does not accept any parameters");
check_lg2(git_stash_pop(repo, 0, NULL),
"Unable to pop stash", NULL);
printf("Dropped refs/stash@{0}\n");
return 0;
}
int lg2_stash(git_repository *repo, int argc, char *argv[])
{
struct opts opts = { 0 };
parse_subcommand(&opts, argc, argv);
switch (opts.cmd) {
case SUBCMD_APPLY:
return cmd_apply(repo, &opts);
case SUBCMD_LIST:
return cmd_list(repo, &opts);
case SUBCMD_PUSH:
return cmd_push(repo, &opts);
case SUBCMD_POP:
return cmd_pop(repo, &opts);
}
return -1;
}
+498
View File
@@ -0,0 +1,498 @@
/*
* libgit2 "status" example - shows how to use the status APIs
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* This example demonstrates the use of the libgit2 status APIs,
* particularly the `git_status_list` object, to roughly simulate the
* output of running `git status`. It serves as a simple example of
* using those APIs to get basic status information.
*
* This does not have:
*
* - Robust error handling
* - Colorized or paginated output formatting
*
* This does have:
*
* - Examples of translating command line arguments to the status
* options settings to mimic `git status` results.
* - A sample status formatter that matches the default "long" format
* from `git status`
* - A sample status formatter that matches the "short" format
*/
enum {
FORMAT_DEFAULT = 0,
FORMAT_LONG = 1,
FORMAT_SHORT = 2,
FORMAT_PORCELAIN = 3,
};
#define MAX_PATHSPEC 8
struct status_opts {
git_status_options statusopt;
char *repodir;
char *pathspec[MAX_PATHSPEC];
int npaths;
int format;
int zterm;
int showbranch;
int showsubmod;
int repeat;
};
static void parse_opts(struct status_opts *o, int argc, char *argv[]);
static void show_branch(git_repository *repo, int format);
static void print_long(git_status_list *status);
static void print_short(git_repository *repo, git_status_list *status);
static int print_submod(git_submodule *sm, const char *name, void *payload);
int lg2_status(git_repository *repo, int argc, char *argv[])
{
git_status_list *status;
struct status_opts o = { GIT_STATUS_OPTIONS_INIT, "." };
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);
if (git_repository_is_bare(repo))
fatal("Cannot report status on bare repository",
git_repository_path(repo));
show_status:
if (o.repeat)
printf("\033[H\033[2J");
/**
* Run status on the repository
*
* We use `git_status_list_new()` to generate a list of status
* information which lets us iterate over it at our
* convenience and extract the data we want to show out of
* each entry.
*
* You can use `git_status_foreach()` or
* `git_status_foreach_ext()` if you'd prefer to execute a
* callback for each entry. The latter gives you more control
* about what results are presented.
*/
check_lg2(git_status_list_new(&status, repo, &o.statusopt),
"Could not get status", NULL);
if (o.showbranch)
show_branch(repo, o.format);
if (o.showsubmod) {
int submod_count = 0;
check_lg2(git_submodule_foreach(repo, print_submod, &submod_count),
"Cannot iterate submodules", o.repodir);
}
if (o.format == FORMAT_LONG)
print_long(status);
else
print_short(repo, status);
git_status_list_free(status);
if (o.repeat) {
sleep(o.repeat);
goto show_status;
}
return 0;
}
/**
* If the user asked for the branch, let's show the short name of the
* branch.
*/
static void show_branch(git_repository *repo, int format)
{
int error = 0;
const char *branch = NULL;
git_reference *head = NULL;
error = git_repository_head(&head, repo);
if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
branch = NULL;
else if (!error) {
branch = git_reference_shorthand(head);
} else
check_lg2(error, "failed to get current branch", NULL);
if (format == FORMAT_LONG)
printf("# On branch %s\n",
branch ? branch : "Not currently on any branch.");
else
printf("## %s\n", branch ? branch : "HEAD (no branch)");
git_reference_free(head);
}
/**
* This function print out an output similar to git's status command
* in long form, including the command-line hints.
*/
static void print_long(git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
const git_status_entry *s;
int header = 0, changes_in_index = 0;
int changed_in_workdir = 0, rm_in_workdir = 0;
const char *old_path, *new_path;
/** Print index changes. */
for (i = 0; i < maxi; ++i) {
char *istatus = NULL;
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT)
continue;
if (s->status & GIT_STATUS_WT_DELETED)
rm_in_workdir = 1;
if (s->status & GIT_STATUS_INDEX_NEW)
istatus = "new file: ";
if (s->status & GIT_STATUS_INDEX_MODIFIED)
istatus = "modified: ";
if (s->status & GIT_STATUS_INDEX_DELETED)
istatus = "deleted: ";
if (s->status & GIT_STATUS_INDEX_RENAMED)
istatus = "renamed: ";
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
istatus = "typechange:";
if (istatus == NULL)
continue;
if (!header) {
printf("# Changes to be committed:\n");
printf("# (use \"git reset HEAD <file>...\" to unstage)\n");
printf("#\n");
header = 1;
}
old_path = s->head_to_index->old_file.path;
new_path = s->head_to_index->new_file.path;
if (old_path && new_path && strcmp(old_path, new_path))
printf("#\t%s %s -> %s\n", istatus, old_path, new_path);
else
printf("#\t%s %s\n", istatus, old_path ? old_path : new_path);
}
if (header) {
changes_in_index = 1;
printf("#\n");
}
header = 0;
/** Print workdir changes to tracked files. */
for (i = 0; i < maxi; ++i) {
char *wstatus = NULL;
s = git_status_byindex(status, i);
/**
* With `GIT_STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
* `index_to_workdir` may not be `NULL` even if there are
* no differences, in which case it will be a `GIT_DELTA_UNMODIFIED`.
*/
if (s->status == GIT_STATUS_CURRENT || s->index_to_workdir == NULL)
continue;
/** Print out the output since we know the file has some changes */
if (s->status & GIT_STATUS_WT_MODIFIED)
wstatus = "modified: ";
if (s->status & GIT_STATUS_WT_DELETED)
wstatus = "deleted: ";
if (s->status & GIT_STATUS_WT_RENAMED)
wstatus = "renamed: ";
if (s->status & GIT_STATUS_WT_TYPECHANGE)
wstatus = "typechange:";
if (wstatus == NULL)
continue;
if (!header) {
printf("# Changes not staged for commit:\n");
printf("# (use \"git add%s <file>...\" to update what will be committed)\n", rm_in_workdir ? "/rm" : "");
printf("# (use \"git checkout -- <file>...\" to discard changes in working directory)\n");
printf("#\n");
header = 1;
}
old_path = s->index_to_workdir->old_file.path;
new_path = s->index_to_workdir->new_file.path;
if (old_path && new_path && strcmp(old_path, new_path))
printf("#\t%s %s -> %s\n", wstatus, old_path, new_path);
else
printf("#\t%s %s\n", wstatus, old_path ? old_path : new_path);
}
if (header) {
changed_in_workdir = 1;
printf("#\n");
}
/** Print untracked files. */
header = 0;
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_WT_NEW) {
if (!header) {
printf("# Untracked files:\n");
printf("# (use \"git add <file>...\" to include in what will be committed)\n");
printf("#\n");
header = 1;
}
printf("#\t%s\n", s->index_to_workdir->old_file.path);
}
}
header = 0;
/** Print ignored files. */
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_IGNORED) {
if (!header) {
printf("# Ignored files:\n");
printf("# (use \"git add -f <file>...\" to include in what will be committed)\n");
printf("#\n");
header = 1;
}
printf("#\t%s\n", s->index_to_workdir->old_file.path);
}
}
if (!changes_in_index && changed_in_workdir)
printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
}
/**
* This version of the output prefixes each path with two status
* columns and shows submodule status information.
*/
static void print_short(git_repository *repo, git_status_list *status)
{
size_t i, maxi = git_status_list_entrycount(status);
const git_status_entry *s;
char istatus, wstatus;
const char *extra, *a, *b, *c;
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT)
continue;
a = b = c = NULL;
istatus = wstatus = ' ';
extra = "";
if (s->status & GIT_STATUS_INDEX_NEW)
istatus = 'A';
if (s->status & GIT_STATUS_INDEX_MODIFIED)
istatus = 'M';
if (s->status & GIT_STATUS_INDEX_DELETED)
istatus = 'D';
if (s->status & GIT_STATUS_INDEX_RENAMED)
istatus = 'R';
if (s->status & GIT_STATUS_INDEX_TYPECHANGE)
istatus = 'T';
if (s->status & GIT_STATUS_WT_NEW) {
if (istatus == ' ')
istatus = '?';
wstatus = '?';
}
if (s->status & GIT_STATUS_WT_MODIFIED)
wstatus = 'M';
if (s->status & GIT_STATUS_WT_DELETED)
wstatus = 'D';
if (s->status & GIT_STATUS_WT_RENAMED)
wstatus = 'R';
if (s->status & GIT_STATUS_WT_TYPECHANGE)
wstatus = 'T';
if (s->status & GIT_STATUS_IGNORED) {
istatus = '!';
wstatus = '!';
}
if (istatus == '?' && wstatus == '?')
continue;
/**
* A commit in a tree is how submodules are stored, so
* let's go take a look at its status.
*/
if (s->index_to_workdir &&
s->index_to_workdir->new_file.mode == GIT_FILEMODE_COMMIT)
{
unsigned int smstatus = 0;
if (!git_submodule_status(&smstatus, repo, s->index_to_workdir->new_file.path,
GIT_SUBMODULE_IGNORE_UNSPECIFIED)) {
if (smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED)
extra = " (new commits)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED)
extra = " (modified content)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED)
extra = " (modified content)";
else if (smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED)
extra = " (untracked content)";
}
}
/**
* Now that we have all the information, format the output.
*/
if (s->head_to_index) {
a = s->head_to_index->old_file.path;
b = s->head_to_index->new_file.path;
}
if (s->index_to_workdir) {
if (!a)
a = s->index_to_workdir->old_file.path;
if (!b)
b = s->index_to_workdir->old_file.path;
c = s->index_to_workdir->new_file.path;
}
if (istatus == 'R') {
if (wstatus == 'R')
printf("%c%c %s %s %s%s\n", istatus, wstatus, a, b, c, extra);
else
printf("%c%c %s %s%s\n", istatus, wstatus, a, b, extra);
} else {
if (wstatus == 'R')
printf("%c%c %s %s%s\n", istatus, wstatus, a, c, extra);
else
printf("%c%c %s%s\n", istatus, wstatus, a, extra);
}
}
for (i = 0; i < maxi; ++i) {
s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_WT_NEW)
printf("?? %s\n", s->index_to_workdir->old_file.path);
}
}
static int print_submod(git_submodule *sm, const char *name, void *payload)
{
int *count = payload;
(void)name;
if (*count == 0)
printf("# Submodules\n");
(*count)++;
printf("# - submodule '%s' at %s\n",
git_submodule_name(sm), git_submodule_path(sm));
return 0;
}
/**
* Parse options that git's status command supports.
*/
static void parse_opts(struct status_opts *o, int argc, char *argv[])
{
struct args_info args = ARGS_INFO_INIT;
for (args.pos = 1; args.pos < argc; ++args.pos) {
char *a = argv[args.pos];
if (a[0] != '-') {
if (o->npaths < MAX_PATHSPEC)
o->pathspec[o->npaths++] = a;
else
fatal("Example only supports a limited pathspec", NULL);
}
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(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(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 if (!strcmp(a, "--repeat"))
o->repeat = 10;
else if (match_int_arg(&o->repeat, &args, "--repeat", 0))
/* okay */;
else if (!strcmp(a, "--list-submodules"))
o->showsubmod = 1;
else
check_lg2(-1, "Unsupported option", a);
}
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;
}
}
+310
View File
@@ -0,0 +1,310 @@
/*
* libgit2 "tag" example - shows how to list, create and delete tags
*
* 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
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "common.h"
/**
* The following example partially reimplements the `git tag` command
* and some of its options.
*
* These commands should work:
* - Tag name listing (`tag`)
* - Filtered tag listing with messages (`tag -n3 -l "v0.1*"`)
* - Lightweight tag creation (`tag test v0.18.0`)
* - Tag creation (`tag -a -m "Test message" test v0.18.0`)
* - Tag deletion (`tag -d test`)
*
* The command line parsing logic is simplified and doesn't handle
* all of the use cases.
*/
/** tag_options represents the parsed command line options */
struct tag_options {
const char *message;
const char *pattern;
const char *tag_name;
const char *target;
int num_lines;
int force;
};
/** tag_state represents the current program state for dragging around */
typedef struct {
git_repository *repo;
struct tag_options *opts;
} tag_state;
/** An action to execute based on the command line arguments */
typedef void (*tag_action)(tag_state *state);
typedef struct args_info args_info;
static void check(int result, const char *message)
{
if (result) fatal(message, NULL);
}
/** Tag listing: Print individual message lines */
static void print_list_lines(const char *message, const tag_state *state)
{
const char *msg = message;
int num = state->opts->num_lines - 1;
if (!msg) return;
/** first line - headline */
while(*msg && *msg != '\n') printf("%c", *msg++);
/** skip over new lines */
while(*msg && *msg == '\n') msg++;
printf("\n");
/** print just headline? */
if (num == 0) return;
if (*msg && msg[1]) printf("\n");
/** print individual commit/tag lines */
while (*msg && num-- >= 2) {
printf(" ");
while (*msg && *msg != '\n') printf("%c", *msg++);
/** handle consecutive new lines */
if (*msg && *msg == '\n' && msg[1] == '\n') {
num--;
printf("\n");
}
while(*msg && *msg == '\n') msg++;
printf("\n");
}
}
/** Tag listing: Print an actual tag object */
static void print_tag(git_tag *tag, const tag_state *state)
{
printf("%-16s", git_tag_name(tag));
if (state->opts->num_lines) {
const char *msg = git_tag_message(tag);
print_list_lines(msg, state);
} else {
printf("\n");
}
}
/** Tag listing: Print a commit (target of a lightweight tag) */
static void print_commit(git_commit *commit, const char *name,
const tag_state *state)
{
printf("%-16s", name);
if (state->opts->num_lines) {
const char *msg = git_commit_message(commit);
print_list_lines(msg, state);
} else {
printf("\n");
}
}
/** Tag listing: Fallback, should not happen */
static void print_name(const char *name)
{
printf("%s\n", name);
}
/** Tag listing: Lookup tags based on ref name and dispatch to print */
static int each_tag(const char *name, tag_state *state)
{
git_repository *repo = state->repo;
git_object *obj;
check_lg2(git_revparse_single(&obj, repo, name),
"Failed to lookup rev", name);
switch (git_object_type(obj)) {
case GIT_OBJECT_TAG:
print_tag((git_tag *) obj, state);
break;
case GIT_OBJECT_COMMIT:
print_commit((git_commit *) obj, name, state);
break;
default:
print_name(name);
}
git_object_free(obj);
return 0;
}
static void action_list_tags(tag_state *state)
{
const char *pattern = state->opts->pattern;
git_strarray tag_names = {0};
size_t i;
check_lg2(git_tag_list_match(&tag_names, pattern ? pattern : "*", state->repo),
"Unable to get list of tags", NULL);
for(i = 0; i < tag_names.count; i++) {
each_tag(tag_names.strings[i], state);
}
git_strarray_free(&tag_names);
}
static void action_delete_tag(tag_state *state)
{
struct tag_options *opts = state->opts;
git_object *obj;
git_buf abbrev_oid = {0};
check(!opts->tag_name, "Name required");
check_lg2(git_revparse_single(&obj, state->repo, opts->tag_name),
"Failed to lookup rev", opts->tag_name);
check_lg2(git_object_short_id(&abbrev_oid, obj),
"Unable to get abbreviated OID", opts->tag_name);
check_lg2(git_tag_delete(state->repo, opts->tag_name),
"Unable to delete tag", opts->tag_name);
printf("Deleted tag '%s' (was %s)\n", opts->tag_name, abbrev_oid.ptr);
git_buf_dispose(&abbrev_oid);
git_object_free(obj);
}
static void action_create_lighweight_tag(tag_state *state)
{
git_repository *repo = state->repo;
struct tag_options *opts = state->opts;
git_oid oid;
git_object *target;
check(!opts->tag_name, "Name required");
if (!opts->target) opts->target = "HEAD";
check(!opts->target, "Target required");
check_lg2(git_revparse_single(&target, repo, opts->target),
"Unable to resolve spec", opts->target);
check_lg2(git_tag_create_lightweight(&oid, repo, opts->tag_name,
target, opts->force), "Unable to create tag", NULL);
git_object_free(target);
}
static void action_create_tag(tag_state *state)
{
git_repository *repo = state->repo;
struct tag_options *opts = state->opts;
git_signature *tagger;
git_oid oid;
git_object *target;
check(!opts->tag_name, "Name required");
check(!opts->message, "Message required");
if (!opts->target) opts->target = "HEAD";
check_lg2(git_revparse_single(&target, repo, opts->target),
"Unable to resolve spec", opts->target);
check_lg2(git_signature_default(&tagger, repo),
"Unable to create signature", NULL);
check_lg2(git_tag_create(&oid, repo, opts->tag_name,
target, tagger, opts->message, opts->force), "Unable to create tag", NULL);
git_object_free(target);
git_signature_free(tagger);
}
static void print_usage(void)
{
fprintf(stderr, "usage: see `git help tag`\n");
exit(1);
}
/** Parse command line arguments and choose action to run when done */
static void parse_options(tag_action *action, struct tag_options *opts, int argc, char **argv)
{
args_info args = ARGS_INFO_INIT;
*action = &action_list_tags;
for (args.pos = 1; args.pos < argc; ++args.pos) {
const char *curr = argv[args.pos];
if (curr[0] != '-') {
if (!opts->tag_name)
opts->tag_name = curr;
else if (!opts->target)
opts->target = curr;
else
print_usage();
if (*action != &action_create_tag)
*action = &action_create_lighweight_tag;
} else if (!strcmp(curr, "-n")) {
opts->num_lines = 1;
*action = &action_list_tags;
} else if (!strcmp(curr, "-a")) {
*action = &action_create_tag;
} else if (!strcmp(curr, "-f")) {
opts->force = 1;
} else if (match_int_arg(&opts->num_lines, &args, "-n", 0)) {
*action = &action_list_tags;
} else if (match_str_arg(&opts->pattern, &args, "-l")) {
*action = &action_list_tags;
} else if (match_str_arg(&opts->tag_name, &args, "-d")) {
*action = &action_delete_tag;
} else if (match_str_arg(&opts->message, &args, "-m")) {
*action = &action_create_tag;
}
}
}
/** Initialize tag_options struct */
static void tag_options_init(struct tag_options *opts)
{
memset(opts, 0, sizeof(*opts));
opts->message = NULL;
opts->pattern = NULL;
opts->tag_name = NULL;
opts->target = NULL;
opts->num_lines = 0;
opts->force = 0;
}
int lg2_tag(git_repository *repo, int argc, char **argv)
{
struct tag_options opts;
tag_action action;
tag_state state;
tag_options_init(&opts);
parse_options(&action, &opts, argc, argv);
state.repo = repo;
state.opts = &opts;
action(&state);
return 0;
}