mirror of
https://github.com/libgit2/libgit2.git
synced 2026-01-25 02:56:17 +00:00
Inside of our networking example code, we have a git2 executable that acts as an entry point to all the different network examples. As such, it is kind of the same like the normal git(1) executable in that it simply arbitrates to the respective subcommands. Let's extend this approach and merge all examples into a single standalone lg2 executable. Instead of building an executable for all the existing examples we have, we now bundle them all inside of the lg2 one and let them be callable via subcommands. In the process, we can get rid of duplicated library initialization, deinitialization and repository discovery code. Instead of having each subcommand handle these on its own, we simply do it inside of the single main function now.
328 lines
9.4 KiB
C
328 lines
9.4 KiB
C
/*
|
|
* 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 'opts' struct captures all the various parsed command line options. */
|
|
struct opts {
|
|
git_diff_options diffopts;
|
|
git_diff_find_options findopts;
|
|
int color;
|
|
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 opts *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 opts *o);
|
|
|
|
int lg2_diff(git_repository *repo, int argc, char *argv[])
|
|
{
|
|
git_tree *t1 = NULL, *t2 = NULL;
|
|
git_diff *diff;
|
|
struct opts o = {
|
|
GIT_DIFF_OPTIONS_INIT, GIT_DIFF_FIND_OPTIONS_INIT,
|
|
-1, 0, 0, GIT_DIFF_FORMAT_PATCH, NULL, NULL, "."
|
|
};
|
|
|
|
parse_opts(&o, argc, argv);
|
|
|
|
/**
|
|
* Possible argument patterns:
|
|
*
|
|
* * <sha1> <sha2>
|
|
* * <sha1> --cached
|
|
* * <sha1>
|
|
* * --cached
|
|
* * --nocache (don't use index data in diff at all)
|
|
* * nothing
|
|
*
|
|
* Currently ranged arguments like <sha1>..<sha2> and <sha1>...<sha2>
|
|
* are not supported in this example
|
|
*/
|
|
|
|
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 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 opts *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;
|
|
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, "--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 opts *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);
|
|
}
|