mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-02 04:47:51 +00:00

Instead of having functions with so very many parameters to pass hunk and line data, this takes the existing git_diff_hunk struct and extends it with more hunk data, plus adds a git_diff_line. Those structs are used to pass back hunk and line data instead of the old APIs that took tons of parameters. Some work that was previously only being done for git_diff_patch creation (scanning the diff content for exact line counts) is now done for all callbacks, but the performance difference should not be noticable.
411 lines
9.9 KiB
C
411 lines
9.9 KiB
C
#include <stdio.h>
|
|
#include <git2.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void check(int error, const char *message, const char *arg)
|
|
{
|
|
if (!error)
|
|
return;
|
|
if (arg)
|
|
fprintf(stderr, "%s '%s' (%d)\n", message, arg, error);
|
|
else
|
|
fprintf(stderr, "%s (%d)\n", message, error);
|
|
exit(1);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
struct log_state {
|
|
git_repository *repo;
|
|
const char *repodir;
|
|
git_revwalk *walker;
|
|
int hide;
|
|
int sorting;
|
|
};
|
|
|
|
static void set_sorting(struct log_state *s, unsigned int sort_mode)
|
|
{
|
|
if (!s->repo) {
|
|
if (!s->repodir) s->repodir = ".";
|
|
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
|
|
"Could not open repository", s->repodir);
|
|
}
|
|
|
|
if (!s->walker)
|
|
check(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);
|
|
}
|
|
|
|
static void push_rev(struct log_state *s, git_object *obj, int hide)
|
|
{
|
|
hide = s->hide ^ hide;
|
|
|
|
if (!s->walker) {
|
|
check(git_revwalk_new(&s->walker, s->repo),
|
|
"Could not create revision walker", NULL);
|
|
git_revwalk_sorting(s->walker, s->sorting);
|
|
}
|
|
|
|
if (!obj)
|
|
check(git_revwalk_push_head(s->walker),
|
|
"Could not find repository HEAD", NULL);
|
|
else if (hide)
|
|
check(git_revwalk_hide(s->walker, git_object_id(obj)),
|
|
"Reference does not refer to a commit", NULL);
|
|
else
|
|
check(git_revwalk_push(s->walker, git_object_id(obj)),
|
|
"Reference does not refer to a commit", NULL);
|
|
|
|
git_object_free(obj);
|
|
}
|
|
|
|
static int add_revision(struct log_state *s, const char *revstr)
|
|
{
|
|
git_revspec revs;
|
|
int hide = 0;
|
|
|
|
if (!s->repo) {
|
|
if (!s->repodir) s->repodir = ".";
|
|
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
|
|
"Could not open repository", s->repodir);
|
|
}
|
|
|
|
if (!revstr) {
|
|
push_rev(s, NULL, hide);
|
|
return 0;
|
|
}
|
|
|
|
if (*revstr == '^') {
|
|
revs.flags = GIT_REVPARSE_SINGLE;
|
|
hide = !hide;
|
|
|
|
if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0)
|
|
return -1;
|
|
} else if (git_revparse(&revs, s->repo, revstr) < 0)
|
|
return -1;
|
|
|
|
if ((revs.flags & GIT_REVPARSE_SINGLE) != 0)
|
|
push_rev(s, revs.from, hide);
|
|
else {
|
|
push_rev(s, revs.to, hide);
|
|
|
|
if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
|
|
git_oid base;
|
|
check(git_merge_base(&base, s->repo,
|
|
git_object_id(revs.from), git_object_id(revs.to)),
|
|
"Could not find merge base", revstr);
|
|
check(git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT),
|
|
"Could not find merge base commit", NULL);
|
|
|
|
push_rev(s, revs.to, hide);
|
|
}
|
|
|
|
push_rev(s, revs.from, !hide);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
gmtime_r(&t, &intm);
|
|
strftime(out, sizeof(out), "%a %b %e %T %Y", &intm);
|
|
|
|
printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes);
|
|
}
|
|
|
|
static void print_commit(git_commit *commit)
|
|
{
|
|
char buf[GIT_OID_HEXSZ + 1];
|
|
int i, count;
|
|
const git_signature *sig;
|
|
const char *scan, *eol;
|
|
|
|
git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
|
|
printf("commit %s\n", buf);
|
|
|
|
if ((count = (int)git_commit_parentcount(commit)) > 1) {
|
|
printf("Merge:");
|
|
for (i = 0; i < count; ++i) {
|
|
git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
|
|
printf(" %s", buf);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
if ((sig = git_commit_author(commit)) != NULL) {
|
|
printf("Author: %s <%s>\n", sig->name, sig->email);
|
|
print_time(&sig->when, "Date: ");
|
|
}
|
|
printf("\n");
|
|
|
|
for (scan = git_commit_message(commit); scan && *scan; ) {
|
|
for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
|
|
|
|
printf(" %.*s\n", (int)(eol - scan), scan);
|
|
scan = *eol ? eol + 1 : NULL;
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static int print_diff(
|
|
const git_diff_delta *delta,
|
|
const git_diff_hunk *hunk,
|
|
const git_diff_line *line,
|
|
void *data)
|
|
{
|
|
(void)delta; (void)hunk; (void)data;
|
|
|
|
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
|
|
line->origin == GIT_DIFF_LINE_ADDITION ||
|
|
line->origin == GIT_DIFF_LINE_DELETION)
|
|
fputc(line->origin, stdout);
|
|
|
|
fwrite(line->content, 1, line->content_len, stdout);
|
|
return 0;
|
|
}
|
|
|
|
static int match_int(int *value, const char *arg, int allow_negative)
|
|
{
|
|
char *found;
|
|
*value = (int)strtol(arg, &found, 10);
|
|
return (found && *found == '\0' && (allow_negative || *value >= 0));
|
|
}
|
|
|
|
static int match_int_arg(
|
|
int *value, const char *arg, const char *pfx, int allow_negative)
|
|
{
|
|
size_t pfxlen = strlen(pfx);
|
|
if (strncmp(arg, pfx, pfxlen) != 0)
|
|
return 0;
|
|
if (!match_int(value, arg + pfxlen, allow_negative))
|
|
usage("Invalid value after argument", arg);
|
|
return 1;
|
|
}
|
|
|
|
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(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
|
|
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
|
|
check(git_commit_tree(&b, commit), "Tree for commit", NULL);
|
|
check(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;
|
|
}
|
|
|
|
struct log_options {
|
|
int show_diff;
|
|
int skip, limit;
|
|
int min_parents, max_parents;
|
|
git_time_t before;
|
|
git_time_t after;
|
|
char *author;
|
|
char *committer;
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int i, count = 0, printed = 0, parents;
|
|
char *a;
|
|
struct log_state s;
|
|
struct log_options opt;
|
|
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
|
|
git_oid oid;
|
|
git_commit *commit = NULL;
|
|
git_pathspec *ps = NULL;
|
|
|
|
git_threads_init();
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
s.sorting = GIT_SORT_TIME;
|
|
|
|
memset(&opt, 0, sizeof(opt));
|
|
opt.max_parents = -1;
|
|
opt.limit = -1;
|
|
|
|
for (i = 1; i < argc; ++i) {
|
|
a = argv[i];
|
|
|
|
if (a[0] != '-') {
|
|
if (!add_revision(&s, a))
|
|
++count;
|
|
else /* try failed revision parse as filename */
|
|
break;
|
|
} else if (!strcmp(a, "--")) {
|
|
++i;
|
|
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 (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
|
|
s.repodir = a + strlen("--git-dir=");
|
|
else if (match_int_arg(&opt.skip, a, "--skip=", 0))
|
|
/* found valid --skip */;
|
|
else if (match_int_arg(&opt.limit, a, "--max-count=", 0))
|
|
/* found valid --max-count */;
|
|
else if (a[1] >= '0' && a[1] <= '9') {
|
|
if (!match_int(&opt.limit, a + 1, 0))
|
|
usage("Invalid limit on number of commits", a);
|
|
} else if (!strcmp(a, "-n")) {
|
|
if (i + 1 == argc || !match_int(&opt.limit, argv[i + 1], 0))
|
|
usage("Argument -n not followed by valid count", argv[i + 1]);
|
|
else
|
|
++i;
|
|
}
|
|
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, a, "--max-parents=", 1))
|
|
/* found valid --max-parents */;
|
|
else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0))
|
|
/* found valid --min_parents */;
|
|
else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch"))
|
|
opt.show_diff = 1;
|
|
else
|
|
usage("Unsupported argument", a);
|
|
}
|
|
|
|
if (!count)
|
|
add_revision(&s, NULL);
|
|
|
|
diffopts.pathspec.strings = &argv[i];
|
|
diffopts.pathspec.count = argc - i;
|
|
if (diffopts.pathspec.count > 0)
|
|
check(git_pathspec_new(&ps, &diffopts.pathspec),
|
|
"Building pathspec", NULL);
|
|
|
|
printed = count = 0;
|
|
|
|
for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
|
|
check(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(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 (count++ < opt.skip)
|
|
continue;
|
|
if (opt.limit != -1 && printed++ >= opt.limit) {
|
|
git_commit_free(commit);
|
|
break;
|
|
}
|
|
|
|
print_commit(commit);
|
|
|
|
if (opt.show_diff) {
|
|
git_tree *a = NULL, *b = NULL;
|
|
git_diff *diff = NULL;
|
|
|
|
if (parents > 1)
|
|
continue;
|
|
check(git_commit_tree(&b, commit), "Get tree", NULL);
|
|
if (parents == 1) {
|
|
git_commit *parent;
|
|
check(git_commit_parent(&parent, commit, 0), "Get parent", NULL);
|
|
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
|
|
git_commit_free(parent);
|
|
}
|
|
|
|
check(git_diff_tree_to_tree(
|
|
&diff, git_commit_owner(commit), a, b, &diffopts),
|
|
"Diff commit with parent", NULL);
|
|
check(git_diff_print(diff, GIT_DIFF_FORMAT_PATCH, print_diff, NULL),
|
|
"Displaying diff", NULL);
|
|
|
|
git_diff_free(diff);
|
|
git_tree_free(a);
|
|
git_tree_free(b);
|
|
}
|
|
}
|
|
|
|
git_pathspec_free(ps);
|
|
git_revwalk_free(s.walker);
|
|
git_repository_free(s.repo);
|
|
git_threads_shutdown();
|
|
|
|
return 0;
|
|
}
|