Root/eeshow/file/git-hist.c

1/*
2 * file/git-hist.c - Retrieve revision history from GIT repo
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13#include <stddef.h>
14#include <stdlib.h>
15#include <stdio.h>
16#include <time.h> /* for vcs_long_for_pango */
17#include <alloca.h>
18
19#include "misc/util.h"
20#include "misc/diag.h"
21#include "file/git-util.h"
22#include "file/git-file.h"
23#include "file/git-hist.h"
24
25
26/*
27 * @@@ we assume to have a single head. That isn't necessarily true, since
28 * each open branch has its own head. Getting this right is for further study.
29 */
30
31
32static struct hist *new_commit(unsigned branch)
33{
34    struct hist *h;
35
36    h = alloc_type(struct hist);
37    h->commit = NULL;
38    h->branch = branch;
39    h->newer = NULL;
40    h->n_newer = 0;
41    h->older = NULL;
42    h->n_older = 0;
43    return h;
44}
45
46
47static void uplink(struct hist *down, struct hist *up)
48{
49    down->newer = realloc_type_n(down->newer, struct hist *,
50        down->n_newer + 1);
51    down->newer[down->n_newer++] = up;
52}
53
54
55static struct hist *find_commit(struct hist *h, const git_commit *commit)
56{
57    unsigned i;
58    struct hist *found;
59
60    /*
61     * @@@ should probably use
62     * git_oid_equal(git_object_id(a), git_object_id(b))
63     */
64    if (h->commit == commit)
65        return h;
66    for (i = 0; i != h->n_older; i++) {
67        if (h->older[i]->newer[0] != h)
68            continue;
69        found = find_commit(h->older[i], commit);
70        if (found)
71            return found;
72    }
73    return NULL;
74}
75
76
77static void recurse(struct hist *h,
78    unsigned n_branches, struct hist **branches)
79{
80    unsigned n, i, j;
81    struct hist **b;
82    const git_error *e;
83
84    n = git_commit_parentcount(h->commit);
85    if (verbose > 2)
86        progress(3, "commit %p: %u + %u", h->commit, n_branches, n);
87
88    b = alloca(sizeof(struct hist) * (n_branches - 1 + n));
89    n_branches--;
90    memcpy(b, branches, sizeof(struct hist *) * n_branches);
91
92    h->older = alloc_type_n(struct hist *, n);
93    h->n_older = n;
94
95    for (i = 0; i != n; i++) {
96        git_commit *commit;
97        struct hist *found = NULL;
98
99        if (git_commit_parent(&commit, h->commit, i)) {
100            e = giterr_last();
101            fatal("git_commit_parent: %s\n", e->message);
102        }
103        for (j = 0; j != n_branches; j++) {
104            found = find_commit(b[j], commit);
105            if (found)
106                break;
107        }
108        if (found) {
109            uplink(found, h);
110            h->older[i] = found;
111        } else {
112            struct hist *new;
113
114            new = new_commit(n_branches);
115            new->commit = commit;
116            h->older[i] = new;
117            b[n_branches++] = new;
118            uplink(new, h);
119            recurse(new, n_branches, b);
120        }
121    }
122}
123
124
125bool vcs_git_try(const char *path)
126{
127    git_repository *repo;
128
129    git_init_once();
130
131    if (git_repository_open_ext(&repo, path,
132        GIT_REPOSITORY_OPEN_CROSS_FS, NULL))
133        return 0;
134    return !git_repository_is_empty(repo);
135}
136
137
138struct hist *vcs_git_hist(const char *path)
139{
140    struct hist *head, *dirty;
141    git_repository *repo;
142    git_oid oid;
143    const git_error *e;
144
145    head = new_commit(0);
146
147    git_init_once();
148
149    if (git_repository_open_ext(&repo, path,
150        GIT_REPOSITORY_OPEN_CROSS_FS, NULL)) {
151        e = giterr_last();
152        fatal("%s: %s\n", path, e->message);
153    }
154
155    if (git_reference_name_to_id(&oid, repo, "HEAD")) {
156        e = giterr_last();
157        fatal("%s: %s\n", git_repository_path(repo), e->message);
158    }
159
160    if (git_commit_lookup(&head->commit, repo, &oid)) {
161        e = giterr_last();
162        fatal("%s: %s\n", git_repository_path(repo), e->message);
163    }
164
165    recurse(head, 1, &head);
166
167    if (!git_repo_is_dirty(repo))
168        return head;
169
170    dirty = new_commit(0);
171    dirty->older = alloc_type(struct hist *);
172    dirty->older[0] = head;
173    dirty->n_older = 1;
174    uplink(head, dirty);
175
176    return dirty;
177}
178
179
180char *vcs_git_get_rev(struct hist *h)
181{
182    const git_oid *oid = git_commit_id(h->commit);
183    char *s = alloc_size(GIT_OID_HEXSZ + 1);
184
185    return git_oid_tostr(s, GIT_OID_HEXSZ + 1, oid);
186}
187
188
189const char *vcs_git_summary(struct hist *h)
190{
191    const char *summary;
192    const git_error *e;
193
194    if (!h->commit)
195        return "Uncommitted changes";
196    summary = git_commit_summary(h->commit);
197    if (summary)
198        return summary;
199
200    e = giterr_last();
201    fatal("git_commit_summary: %s\n", e->message);
202}
203
204
205/*
206 * @@@ This one is a bit inconvenient. It depends both on the information the
207 * VCS provides, some of which is fairly generic, but some may not be, and
208 * the very specific constraints imposed by the markup format of Pango.
209 */
210
211char *vcs_git_long_for_pango(struct hist *h,
212    char *(*formatter)(const char *fmt, ...))
213{
214    const git_error *e;
215    git_buf buf = { 0 };
216    time_t commit_time;
217    const git_signature *sig;
218    char *s;
219
220    if (!h->commit)
221        return stralloc("Uncommitted changes");
222    if (git_object_short_id(&buf, (git_object *) h->commit))
223        goto fail;
224    commit_time = git_commit_time(h->commit);
225    sig = git_commit_committer(h->commit);
226    s = formatter("<b>%s</b> %s%s &lt;%s&gt;<small>\n%s</small>",
227        buf.ptr, ctime(&commit_time), sig->name, sig->email,
228        git_commit_summary(h->commit));
229    git_buf_free(&buf);
230    return s;
231
232fail:
233    e = giterr_last();
234    fatal("vcs_git_long_for_pango: %s\n", e->message);
235}
236
237
238void hist_iterate(struct hist *h,
239    void (*fn)(void *user, struct hist *h), void *user)
240{
241    unsigned i;
242
243    fn(user, h);
244    for (i = 0; i != h->n_older; i++)
245        if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
246            hist_iterate(h->older[i], fn, user);
247}
248
249
250void dump_hist(struct hist *h)
251{
252    git_buf buf = { 0 };
253    const git_error *e;
254    unsigned i;
255
256    if (h->commit) {
257        if (git_object_short_id(&buf, (git_object *) h->commit)) {
258            e = giterr_last();
259            fatal("git_object_short_id: %s\n", e->message);
260        }
261        printf("%*s%s %s\n",
262            2 * h->branch, "", buf.ptr, vcs_git_summary(h));
263        git_buf_free(&buf);
264    } else {
265        printf("dirty\n");
266    }
267
268    for (i = 0; i != h->n_older; i++)
269        if (h->older[i]->newer[h->older[i]->n_newer - 1] == h)
270            dump_hist(h->older[i]);
271}
272

Archive Download this file

Branches:
master



interactive