2016-08-04 22:12:40 +03:00
|
|
|
/*
|
2016-08-18 03:07:13 +03:00
|
|
|
* file/git-hist.c - Retrieve revision history from GIT repo
|
2016-08-04 22:12:40 +03:00
|
|
|
*
|
|
|
|
* Written 2016 by Werner Almesberger
|
|
|
|
* Copyright 2016 by Werner Almesberger
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
2016-08-07 11:09:34 +03:00
|
|
|
#include <time.h> /* for vcs_long_for_pango */
|
2016-08-04 22:12:40 +03:00
|
|
|
#include <alloca.h>
|
|
|
|
|
2016-08-18 03:37:15 +03:00
|
|
|
#include "misc/util.h"
|
|
|
|
#include "misc/diag.h"
|
2016-08-18 03:07:13 +03:00
|
|
|
#include "file/git-util.h"
|
|
|
|
#include "file/git-file.h"
|
|
|
|
#include "file/git-hist.h"
|
2016-08-04 22:12:40 +03:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @@@ we assume to have a single head. That isn't necessarily true, since
|
|
|
|
* each open branch has its own head. Getting this right is for further study.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
static struct hist *new_commit(unsigned branch)
|
|
|
|
{
|
|
|
|
struct hist *h;
|
|
|
|
|
|
|
|
h = alloc_type(struct hist);
|
|
|
|
h->commit = NULL;
|
|
|
|
h->branch = branch;
|
|
|
|
h->newer = NULL;
|
|
|
|
h->n_newer = 0;
|
|
|
|
h->older = NULL;
|
|
|
|
h->n_older = 0;
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void uplink(struct hist *down, struct hist *up)
|
|
|
|
{
|
2016-08-23 03:00:10 +03:00
|
|
|
down->newer = realloc_type_n(down->newer, struct hist *,
|
|
|
|
down->n_newer + 1);
|
2016-08-04 22:12:40 +03:00
|
|
|
down->newer[down->n_newer++] = up;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static struct hist *find_commit(struct hist *h, const git_commit *commit)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
struct hist *found;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* @@@ should probably use
|
|
|
|
* git_oid_equal(git_object_id(a), git_object_id(b))
|
|
|
|
*/
|
|
|
|
if (h->commit == commit)
|
|
|
|
return h;
|
|
|
|
for (i = 0; i != h->n_older; i++) {
|
|
|
|
if (h->older[i]->newer[0] != h)
|
|
|
|
continue;
|
|
|
|
found = find_commit(h->older[i], commit);
|
|
|
|
if (found)
|
|
|
|
return found;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void recurse(struct hist *h,
|
|
|
|
unsigned n_branches, struct hist **branches)
|
|
|
|
{
|
|
|
|
unsigned n, i, j;
|
|
|
|
struct hist **b;
|
|
|
|
const git_error *e;
|
|
|
|
|
|
|
|
n = git_commit_parentcount(h->commit);
|
|
|
|
if (verbose > 2)
|
2016-08-22 11:06:05 +03:00
|
|
|
progress(3, "commit %p: %u + %u", h->commit, n_branches, n);
|
2016-08-04 22:12:40 +03:00
|
|
|
|
|
|
|
b = alloca(sizeof(struct hist) * (n_branches - 1 + n));
|
|
|
|
n_branches--;
|
|
|
|
memcpy(b, branches, sizeof(struct hist *) * n_branches);
|
|
|
|
|
2016-08-11 02:28:31 +03:00
|
|
|
h->older = alloc_type_n(struct hist *, n);
|
2016-08-04 22:12:40 +03:00
|
|
|
h->n_older = n;
|
|
|
|
|
|
|
|
for (i = 0; i != n; i++) {
|
|
|
|
git_commit *commit;
|
|
|
|
struct hist *found = NULL;
|
|
|
|
|
|
|
|
if (git_commit_parent(&commit, h->commit, i)) {
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("git_commit_parent: %s\n", e->message);
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
for (j = 0; j != n_branches; j++) {
|
|
|
|
found = find_commit(b[j], commit);
|
|
|
|
if (found)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
uplink(found, h);
|
|
|
|
h->older[i] = found;
|
|
|
|
} else {
|
|
|
|
struct hist *new;
|
|
|
|
|
|
|
|
new = new_commit(n_branches);
|
|
|
|
new->commit = commit;
|
|
|
|
h->older[i] = new;
|
|
|
|
b[n_branches++] = new;
|
|
|
|
uplink(new, h);
|
|
|
|
recurse(new, n_branches, b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-05 02:35:41 +03:00
|
|
|
bool vcs_git_try(const char *path)
|
|
|
|
{
|
|
|
|
git_repository *repo;
|
|
|
|
|
2016-08-05 14:56:08 +03:00
|
|
|
git_init_once();
|
2016-08-05 02:35:41 +03:00
|
|
|
|
2016-08-05 14:41:33 +03:00
|
|
|
if (git_repository_open_ext(&repo, path,
|
|
|
|
GIT_REPOSITORY_OPEN_CROSS_FS, NULL))
|
|
|
|
return 0;
|
|
|
|
return !git_repository_is_empty(repo);
|
2016-08-05 02:35:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-04 22:12:40 +03:00
|
|
|
struct hist *vcs_git_hist(const char *path)
|
|
|
|
{
|
2016-08-05 15:22:37 +03:00
|
|
|
struct hist *head, *dirty;
|
2016-08-04 22:12:40 +03:00
|
|
|
git_repository *repo;
|
|
|
|
git_oid oid;
|
|
|
|
const git_error *e;
|
|
|
|
|
|
|
|
head = new_commit(0);
|
|
|
|
|
2016-08-05 14:56:08 +03:00
|
|
|
git_init_once();
|
2016-08-05 01:40:29 +03:00
|
|
|
|
2016-08-04 22:12:40 +03:00
|
|
|
if (git_repository_open_ext(&repo, path,
|
|
|
|
GIT_REPOSITORY_OPEN_CROSS_FS, NULL)) {
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("%s: %s\n", path, e->message);
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (git_reference_name_to_id(&oid, repo, "HEAD")) {
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("%s: %s\n", git_repository_path(repo), e->message);
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (git_commit_lookup(&head->commit, repo, &oid)) {
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("%s: %s\n", git_repository_path(repo), e->message);
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
recurse(head, 1, &head);
|
2016-08-05 15:22:37 +03:00
|
|
|
|
|
|
|
if (!git_repo_is_dirty(repo))
|
|
|
|
return head;
|
|
|
|
|
|
|
|
dirty = new_commit(0);
|
|
|
|
dirty->older = alloc_type(struct hist *);
|
|
|
|
dirty->older[0] = head;
|
|
|
|
dirty->n_older = 1;
|
|
|
|
uplink(head, dirty);
|
|
|
|
|
|
|
|
return dirty;
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-06 07:18:43 +03:00
|
|
|
char *vcs_git_get_rev(struct hist *h)
|
|
|
|
{
|
|
|
|
const git_oid *oid = git_commit_id(h->commit);
|
|
|
|
char *s = alloc_size(GIT_OID_HEXSZ + 1);
|
|
|
|
|
|
|
|
return git_oid_tostr(s, GIT_OID_HEXSZ + 1, oid);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-04 22:12:40 +03:00
|
|
|
const char *vcs_git_summary(struct hist *h)
|
|
|
|
{
|
|
|
|
const char *summary;
|
|
|
|
const git_error *e;
|
|
|
|
|
2016-08-05 15:22:37 +03:00
|
|
|
if (!h->commit)
|
|
|
|
return "Uncommitted changes";
|
2016-08-04 22:12:40 +03:00
|
|
|
summary = git_commit_summary(h->commit);
|
|
|
|
if (summary)
|
|
|
|
return summary;
|
|
|
|
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("git_commit_summary: %s\n", e->message);
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-07 11:09:34 +03:00
|
|
|
/*
|
|
|
|
* @@@ This one is a bit inconvenient. It depends both on the information the
|
|
|
|
* VCS provides, some of which is fairly generic, but some may not be, and
|
|
|
|
* the very specific constraints imposed by the markup format of Pango.
|
|
|
|
*/
|
|
|
|
|
2016-08-18 03:00:02 +03:00
|
|
|
char *vcs_git_long_for_pango(struct hist *h,
|
|
|
|
char *(*formatter)(const char *fmt, ...))
|
2016-08-07 11:09:34 +03:00
|
|
|
{
|
|
|
|
const git_error *e;
|
|
|
|
git_buf buf = { 0 };
|
|
|
|
time_t commit_time;
|
|
|
|
const git_signature *sig;
|
|
|
|
char *s;
|
|
|
|
|
2016-08-11 09:24:34 +03:00
|
|
|
if (!h->commit)
|
|
|
|
return stralloc("Uncommitted changes");
|
2016-08-07 11:09:34 +03:00
|
|
|
if (git_object_short_id(&buf, (git_object *) h->commit))
|
|
|
|
goto fail;
|
|
|
|
commit_time = git_commit_time(h->commit);
|
|
|
|
sig = git_commit_committer(h->commit);
|
2016-08-18 03:00:02 +03:00
|
|
|
s = formatter("<b>%s</b> %s%s <%s><small>\n%s</small>",
|
2016-08-07 11:09:34 +03:00
|
|
|
buf.ptr, ctime(&commit_time), sig->name, sig->email,
|
|
|
|
git_commit_summary(h->commit));
|
|
|
|
git_buf_free(&buf);
|
|
|
|
return s;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("vcs_git_long_for_pango: %s\n", e->message);
|
2016-08-07 11:09:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-05 02:35:41 +03:00
|
|
|
void hist_iterate(struct hist *h,
|
|
|
|
void (*fn)(void *user, struct hist *h), void *user)
|
|
|
|
{
|
|
|
|
unsigned i;
|
|
|
|
|
|
|
|
fn(user, h);
|
|
|
|
for (i = 0; i != h->n_older; i++)
|
|
|
|
if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
|
|
|
|
hist_iterate(h->older[i], fn, user);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-08-04 22:12:40 +03:00
|
|
|
void dump_hist(struct hist *h)
|
|
|
|
{
|
|
|
|
git_buf buf = { 0 };
|
|
|
|
const git_error *e;
|
|
|
|
unsigned i;
|
|
|
|
|
2016-08-05 15:22:37 +03:00
|
|
|
if (h->commit) {
|
|
|
|
if (git_object_short_id(&buf, (git_object *) h->commit)) {
|
|
|
|
e = giterr_last();
|
2016-08-11 23:19:32 +03:00
|
|
|
fatal("git_object_short_id: %s\n", e->message);
|
2016-08-05 15:22:37 +03:00
|
|
|
}
|
|
|
|
printf("%*s%s %s\n",
|
|
|
|
2 * h->branch, "", buf.ptr, vcs_git_summary(h));
|
|
|
|
git_buf_free(&buf);
|
|
|
|
} else {
|
|
|
|
printf("dirty\n");
|
2016-08-04 22:12:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i != h->n_older; i++)
|
|
|
|
if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
|
|
|
|
dump_hist(h->older[i]);
|
|
|
|
}
|